├── .github
├── labeler.yml
├── release-drafter.yml
└── workflows
│ ├── build.yml
│ ├── labeler.yml
│ ├── release.yml
│ └── scala-steward.yml
├── .gitignore
├── .scala-steward.conf
├── .travis.yml
├── LICENSE.txt
├── README.md
├── bin
├── installDynamoDbLocal
└── runDynamoDbLocal
├── build.sbt
├── core
└── src
│ ├── main
│ └── scala
│ │ └── awscala
│ │ ├── Action.scala
│ │ ├── AvailabilityZone.scala
│ │ ├── BasicCredentialsProvider.scala
│ │ ├── Condition.scala
│ │ ├── Credentials.scala
│ │ ├── CredentialsLoader.scala
│ │ ├── CredentialsProvider.scala
│ │ ├── DateTime.scala
│ │ ├── DefaultCredentialsProvider.scala
│ │ ├── Effect.scala
│ │ ├── Policy.scala
│ │ ├── Region0.scala
│ │ ├── Resource.scala
│ │ ├── Sequencer.scala
│ │ ├── Statement.scala
│ │ └── package.scala
│ └── test
│ ├── resources
│ └── logback.xml
│ └── scala
│ └── awscala
│ └── RegionSpec.scala
├── dynamodb
└── src
│ ├── main
│ ├── scala-2
│ │ └── awscala
│ │ │ └── dynamodbv2
│ │ │ └── TableCompat.scala
│ ├── scala-3
│ │ └── awscala
│ │ │ └── dynamodbv2
│ │ │ └── TableCompat.scala
│ └── scala
│ │ └── awscala
│ │ └── dynamodbv2
│ │ ├── Attribute.scala
│ │ ├── AttributeAction.scala
│ │ ├── AttributeDefinition.scala
│ │ ├── AttributeType.scala
│ │ ├── AttributeValue.scala
│ │ ├── BillingMode.scala
│ │ ├── DynamoDB.scala
│ │ ├── DynamoDBCondition.scala
│ │ ├── DynamoDBImplicits.scala
│ │ ├── GlobalSecondaryIndex.scala
│ │ ├── Item.scala
│ │ ├── KeySchema.scala
│ │ ├── KeyType.scala
│ │ ├── LocalSecondaryIndex.scala
│ │ ├── PageStats.scala
│ │ ├── Projection.scala
│ │ ├── ProjectionType.scala
│ │ ├── ProvisionedThroughput.scala
│ │ ├── ResultPager.scala
│ │ ├── SecondaryIndex.scala
│ │ ├── Table.scala
│ │ ├── TableMeta.scala
│ │ └── package.scala
│ └── test
│ ├── scala-2
│ └── awscala
│ │ └── DynamoDBV2AnnotationsSpec.scala
│ └── scala
│ └── awscala
│ └── DynamoDBV2Spec.scala
├── ec2
└── src
│ ├── main
│ └── scala
│ │ └── awscala
│ │ └── ec2
│ │ ├── EC2.scala
│ │ ├── Instance.scala
│ │ ├── InstanceType0.scala
│ │ ├── InstanceWithKeyPair.scala
│ │ ├── Requests.scala
│ │ ├── SSHEnabledInstance.scala
│ │ └── package.scala
│ └── test
│ └── scala
│ └── awscala
│ ├── EC2Spec.scala
│ └── KeyPairSpec.scala
├── emr
└── src
│ ├── main
│ └── scala
│ │ └── awscala
│ │ └── emr
│ │ ├── Cluster.scala
│ │ └── EMR.scala
│ └── test
│ └── scala
│ └── awscala
│ └── EMRSpec.scala
├── iam
└── src
│ ├── main
│ └── scala
│ │ └── awscala
│ │ └── iam
│ │ ├── AccessKey.scala
│ │ ├── Group.scala
│ │ ├── GroupPolicy.scala
│ │ ├── IAM.scala
│ │ ├── InstanceProfile.scala
│ │ ├── LoginProfile.scala
│ │ ├── Role.scala
│ │ ├── RolePolicy.scala
│ │ ├── User.scala
│ │ ├── UserPolicy.scala
│ │ ├── VirtualMFADevice.scala
│ │ └── package.scala
│ └── test
│ └── scala
│ └── awscala
│ └── IAMSpec.scala
├── project
├── build.properties
└── plugins.sbt
├── redshift
└── src
│ └── main
│ └── scala
│ └── awscala
│ └── redshift
│ ├── AccountWithRestoreAccess.scala
│ ├── Cluster.scala
│ ├── ClusterParameterGroup.scala
│ ├── ClusterParameterGroupStatus.scala
│ ├── ClusterSecurityGroup.scala
│ ├── ClusterSecurityGroupMembership.scala
│ ├── ClusterSubnetGroup.scala
│ ├── ClusterType.scala
│ ├── ClusterVersion.scala
│ ├── EC2SecurityGroup.scala
│ ├── Endpoint.scala
│ ├── Event.scala
│ ├── IPRange.scala
│ ├── NewCluster.scala
│ ├── NodeType.scala
│ ├── PendingModifiedValues.scala
│ ├── Redshift.scala
│ ├── ReservedNode.scala
│ ├── RestoreStatus.scala
│ ├── Snapshot.scala
│ ├── SnapshotType.scala
│ ├── Status.scala
│ ├── Subnet.scala
│ └── VpcSecurityGroupMembership.scala
├── release.sh
├── s3
└── src
│ ├── main
│ └── scala
│ │ └── awscala
│ │ └── s3
│ │ ├── AccessControlList.scala
│ │ ├── Bucket.scala
│ │ ├── Grant.scala
│ │ ├── Grantee.scala
│ │ ├── Owner.scala
│ │ ├── PutObjectResult.scala
│ │ ├── S3.scala
│ │ ├── S3Object.scala
│ │ ├── S3ObjectSummary.scala
│ │ └── package.scala
│ └── test
│ └── scala
│ └── awscala
│ └── S3Spec.scala
├── simpledb
└── src
│ ├── main
│ └── scala
│ │ └── awscala
│ │ └── simpledb
│ │ ├── Attribute.scala
│ │ ├── Domain.scala
│ │ ├── DomainMetadata.scala
│ │ ├── Item.scala
│ │ └── SimpleDB.scala
│ └── test
│ └── scala
│ └── awscala
│ └── SimpleDBSpec.scala
├── sqs
└── src
│ ├── main
│ └── scala
│ │ └── awscala
│ │ └── sqs
│ │ ├── DeleteMessageBatchEntry.scala
│ │ ├── Message.scala
│ │ ├── MessageBatchEntry.scala
│ │ ├── Queue.scala
│ │ ├── SQS.scala
│ │ └── package.scala
│ └── test
│ └── scala
│ └── awscala
│ └── SQSSpec.scala
├── stepfunctions
└── src
│ ├── main
│ └── scala
│ │ └── awscala
│ │ └── stepfunctions
│ │ ├── Activity.scala
│ │ ├── ArnFormat.scala
│ │ ├── Execution.scala
│ │ ├── ExecutionDetails.scala
│ │ ├── ExecutionEvent.scala
│ │ ├── ExecutionEventDetails.scala
│ │ ├── ExecutionStatus.scala
│ │ ├── StateMachine.scala
│ │ └── StepFunctions.scala
│ └── test
│ └── scala
│ └── awscala
│ └── StepFunctionsSpec.scala
└── sts
└── src
├── main
└── scala
│ └── awscala
│ └── sts
│ ├── FederatedUser.scala
│ ├── FederationToken.scala
│ ├── STS.scala
│ ├── SessionToken.scala
│ └── TemporaryCredentials.scala
└── test
└── scala
└── awscala
└── STSSpec.scala
/.github/labeler.yml:
--------------------------------------------------------------------------------
1 | feature: ['feature/*', 'enhancement/*']
2 | bugfix: ['bugfix/*', 'fix/*']
3 | update: update/*
4 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | categories:
2 | - title: '🚀 Features'
3 | labels:
4 | - 'feature'
5 | - 'enhancement'
6 | - title: '🐛 Fixes'
7 | labels:
8 | - 'bugfix'
9 | - 'fix'
10 | - title: '🌱 Updates'
11 | labels:
12 | - 'update'
13 | - 'dependency-update'
14 | exclude-labels:
15 | - 'skip-changelog'
16 | - 'invalid'
17 | template: |
18 | ## 📢 What's changed?
19 |
20 | $CHANGES
21 |
22 | ## 👬 Contributors
23 |
24 | $CONTRIBUTORS
25 |
26 | ## 🔙 Previous
27 |
28 | $PREVIOUS_TAG
29 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | timeout-minutes: 10
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Set up JDK 1.8
16 | uses: actions/setup-java@v1
17 | with:
18 | java-version: 1.8
19 | - name: Cache sbt
20 | uses: actions/cache@v2
21 | with:
22 | path: |
23 | ~/.sbt
24 | ~/.ivy2/cache
25 | ~/.coursier
26 | ~/.cache/coursier
27 | key: sbt-cache-${{ runner.os }}-${{ matrix.target-platform }}-${{ hashFiles('project/build.properties') }}
28 | - name: Build
29 | run: sbt compile #TODO: run tests
30 |
--------------------------------------------------------------------------------
/.github/workflows/labeler.yml:
--------------------------------------------------------------------------------
1 | name: labeler
2 |
3 | on: [ pull_request ]
4 |
5 | jobs:
6 | labeler:
7 | runs-on: ubuntu-latest
8 | timeout-minutes: 10
9 | steps:
10 | - name: Label the PR action
11 | uses: TimonVS/pr-labeler-action@v3
12 | with:
13 | configuration-path: .github/labeler.yml
14 | env:
15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | push:
5 | tags: [v*]
6 | workflow_dispatch:
7 |
8 | jobs:
9 | release:
10 | runs-on: ubuntu-latest
11 | timeout-minutes: 10
12 | steps:
13 | - uses: actions/checkout@v2
14 | with:
15 | fetch-depth: 0
16 | - name: Set up JDK 1.8
17 | uses: actions/setup-java@v1
18 | with:
19 | java-version: 1.8
20 | - name: Cache sbt
21 | uses: actions/cache@v2
22 | with:
23 | path: |
24 | ~/.sbt
25 | ~/.ivy2/cache
26 | ~/.coursier
27 | ~/.cache/coursier
28 | key: sbt-cache-${{ runner.os }}-${{ matrix.target-platform }}-${{ hashFiles('project/build.properties') }}
29 | - name: Extract latest version
30 | run: echo "VERSION=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
31 | - name: Publish release notes
32 | uses: release-drafter/release-drafter@v5
33 | with:
34 | config-name: release-drafter.yml
35 | publish: true
36 | name: "${{ env.VERSION }}"
37 | tag: "${{ env.VERSION }}"
38 | version: "${{ env.VERSION }}"
39 | env:
40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 |
--------------------------------------------------------------------------------
/.github/workflows/scala-steward.yml:
--------------------------------------------------------------------------------
1 | name: scala-steward
2 |
3 | on:
4 | schedule:
5 | - cron: "0 0 * * 0"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | scala-steward:
10 | runs-on: ubuntu-latest
11 | timeout-minutes: 10
12 | steps:
13 | - uses: actions/checkout@v2
14 | - name: Execute Scala Steward
15 | uses: scala-steward-org/scala-steward-action@v2
16 | with:
17 | github-token: ${{ secrets.GITHUB_TOKEN }}
18 | author-email: scala-steward@users.noreply.github.com
19 | author-name: Scala Steward
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .settings
2 | *.db
3 | .classpath
4 | .project
5 | *.iml
6 | *.ipr
7 | *.iws
8 | dist/
9 | lib_managed/
10 | project/boot/
11 | project/plugins/project/
12 | target/
13 |
14 | # use glob syntax.
15 | syntax: glob
16 | *.ser
17 | *.class
18 | *~
19 | *.bak
20 | #*.off
21 | *.old
22 |
23 | # eclipse conf file
24 | .settings
25 | .classpath
26 | .project
27 | .manager
28 | .scala_dependencies
29 |
30 | # idea
31 | .idea
32 | *.iml
33 |
34 | # vscode
35 | .bloop
36 | .metals
37 | project/.bloop
38 |
39 | # building
40 | target
41 | build
42 | null
43 | tmp*
44 | temp*
45 | dist
46 | test-output
47 | build.log
48 | .cache
49 |
50 | # other scm
51 | .svn
52 | .CVS
53 | .hg*
54 |
55 | # switch to regexp syntax.
56 | # syntax: regexp
57 | # ^\.pc/
58 |
59 | #SHITTY output not in target directory
60 | build.log
61 | .DS_Store
62 | derby.log
63 |
64 | db
65 |
66 | .lib
67 | sbt-launch.jar
68 |
69 | logs
70 |
71 | awscala-unit-test-keypair-*
72 |
73 | .ensime*
74 |
75 | .dynamodb-local
76 | /.bsp/
77 |
78 | .vscode/
79 |
--------------------------------------------------------------------------------
/.scala-steward.conf:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: scala
2 | jdk:
3 | - openjdk8
4 | - openjdk11
5 | scala:
6 | - 2.13.4
7 | script:
8 | - sbt "++${TRAVIS_SCALA_VERSION}!" test:compile dynamodb/test
9 | - git diff --exit-code
10 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright AWScala Developers
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
12 | either express or implied. See the License for the specific language
13 | governing permissions and limitations under the License.
14 |
15 |
16 |
--------------------------------------------------------------------------------
/bin/installDynamoDbLocal:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # **** Only tested on Ubuntu 14.04 ****
4 |
5 | # Install DynamoDB Local unit test dependency
6 | # Usage:installDynamoDbLocal [DIR]
7 | # Where DIR defaults to /opt
8 |
9 | set -e
10 |
11 | if [ $# == 0 ]; then PARENT=/opt; else PARENT="$1"; fi
12 | TMP_DIR=/var/tmp/dynamoDB
13 | TMP_FILE=$TMP_DIR/dynamo.tar.gz
14 | DYNAMO_DIR=DynamoDBLocal
15 | DEST=$PARENT/$DYNAMO_DIR
16 |
17 | mkdir -p $TMP_DIR
18 | wget https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_latest.tar.gz -O $TMP_FILE
19 |
20 | cd $TMP_DIR
21 | if [ -d "$DEST/" ]; then
22 | echo "Cleaning $DEST/"
23 | rm -rf $DEST/*
24 | fi
25 | mkdir -p $DEST
26 | tar xzf $TMP_FILE && mv *.jar $DEST/ && mv DynamoDBLocal_lib/ $DEST/
27 | cd - >/dev/null
28 | rm -rf $TMP_DIR
29 | echo "Installed to $DEST/"
30 |
31 |
--------------------------------------------------------------------------------
/bin/runDynamoDbLocal:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Run DynamoDB Local unit test dependency
4 | # Usage:launchDynamoDbLocal [DIR]
5 | # Where DIR defaults to /opt
6 |
7 | if [ $# == 0 ]; then DEST=/opt; else DEST="$1"; fi
8 | DIR=$DEST/DynamoDBLocal
9 |
10 | java -Djava.library.path="$DIR" -jar $DIR/DynamoDBLocal.jar
11 |
12 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | import xerial.sbt.Sonatype.autoImport._
2 |
3 | val scala213 = "2.13.10"
4 | val scala3 = "3.2.2"
5 |
6 | lazy val commonSettings = Seq(
7 | organization := "com.github.seratch",
8 | name := "awscala",
9 | version := "0.9.2",
10 | scalaVersion := scala213,
11 | crossScalaVersions := Seq(scala213, scala3),
12 | sbtPlugin := false,
13 | transitiveClassifiers in Global := Seq(Artifact.SourceClassifier),
14 | scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature"),
15 | publishTo := _publishTo(version.value),
16 | publishMavenStyle := true,
17 | publishArtifact in Test := false,
18 | pomIncludeRepository := { _ => false },
19 | pomExtra := https://github.com/seratch/awscala
20 |
21 |
22 | Apache License, Version 2.0
23 | http://www.apache.org/licenses/LICENSE-2.0.html
24 | repo
25 |
26 |
27 |
28 | git@github.com:seratch/awscala.git
29 | scm:git:git@github.com:seratch/awscala.git
30 |
31 |
32 |
33 | seratch
34 | Kazuhiro Sera
35 | http://seratch.net/
36 |
37 |
38 | mslinn
39 | Mike Slinn
40 | https://github.com/mslinn
41 |
42 |
43 | Rheeseyb
44 | RheeseyB
45 | https://github.com/Rheeseyb
46 |
47 |
48 | gribeiro
49 | Georges Kovacs Ribeiro
50 | https://github.com/gribeiro
51 |
52 | ,
53 | organization := "com.github.seratch",
54 | sonatypeProfileName := "com.github.seratch"
55 | )
56 |
57 | lazy val awsJavaSdkVersion = "1.12.349"
58 |
59 | lazy val all = (project in file("."))
60 | .settings(commonSettings)
61 | .settings(
62 | moduleName := "awscala"
63 | )
64 | .aggregate(core, dynamodb, ec2, emr, iam, redshift, s3, simpledb, sqs, sts, stepfunctions)
65 | .dependsOn(core, dynamodb, ec2, emr, iam, redshift, s3, simpledb, sqs, sts, stepfunctions)
66 |
67 | lazy val core = project
68 | .in(file("core"))
69 | .settings(commonSettings)
70 | .settings(
71 | moduleName := "awscala-core",
72 | libraryDependencies ++= Seq(
73 | "com.amazonaws" % "aws-java-sdk-core" % awsJavaSdkVersion,
74 | "joda-time" % "joda-time" % "2.12.4",
75 | "org.joda" % "joda-convert" % "2.2.3",
76 | "org.scala-lang.modules" %% "scala-collection-compat" % "2.9.0",
77 | "org.bouncycastle" % "bcprov-jdk16" % "1.46" % "provided",
78 | "ch.qos.logback" % "logback-classic" % "1.4.6" % "test",
79 | "org.scalatest" %% "scalatest" % "3.2.15" % "test",
80 | ) ++ {scalaVersion.value.head match {
81 | case '2' => Seq("org.scala-lang" % "scala-reflect" % scalaVersion.value)
82 | case _ => Seq()
83 | }}
84 | )
85 |
86 | lazy val ec2 = awsProject("ec2")
87 | .settings(
88 | libraryDependencies ++= Seq(
89 | "com.decodified" %% "scala-ssh" % "0.11.1" % "provided"
90 | )
91 | )
92 |
93 | lazy val iam = awsProject("iam")
94 | lazy val dynamodb = awsProject("dynamodb").settings(dynamoTestSettings)
95 | lazy val emr = awsProject("emr").dependsOn(ec2 % "test")
96 | lazy val redshift = awsProject("redshift")
97 | lazy val s3 = awsProject("s3")
98 | lazy val simpledb = awsProject("simpledb")
99 | lazy val sqs = awsProject("sqs")
100 | lazy val sts = awsProject("sts")
101 | lazy val stepfunctions = awsProject("stepfunctions").dependsOn(iam % "test")
102 |
103 | def awsProject(service: String) = {
104 | Project
105 | .apply(service, file(service))
106 | .settings(commonSettings)
107 | .settings(
108 | moduleName := s"awscala-$service",
109 | libraryDependencies ++= Seq(
110 | "com.amazonaws" % s"aws-java-sdk-$service" % awsJavaSdkVersion,
111 | "ch.qos.logback" % "logback-classic" % "1.4.6" % "test",
112 | "org.scalatest" %% "scalatest" % "3.2.15" % "test"
113 | )
114 | )
115 | .dependsOn(core)
116 | }
117 |
118 | lazy val dynamoTestSettings = Seq(
119 | dynamoDBLocalDownloadDir := file(".dynamodb-local"),
120 | dynamoDBLocalPort := 8000,
121 | startDynamoDBLocal := startDynamoDBLocal.dependsOn(compile in Test).value,
122 | test in Test := (test in Test).dependsOn(startDynamoDBLocal).value,
123 | testOptions in Test += dynamoDBLocalTestCleanup.value,
124 | parallelExecution in Test := false
125 | )
126 |
127 | def _publishTo(v: String) = {
128 | val nexus = "https://oss.sonatype.org/"
129 | if (v.trim.endsWith("SNAPSHOT"))
130 | Some("snapshots" at nexus + "content/repositories/snapshots")
131 | else
132 | Some("releases" at nexus + "service/local/staging/deploy/maven2")
133 | }
134 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/Action.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import com.amazonaws.auth.{ policy => aws }
4 |
5 | case class Action(name: String) extends aws.Action {
6 | override def getActionName = name
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/AvailabilityZone.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | case class AvailabilityZone(name: String)
4 |
5 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/BasicCredentialsProvider.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | class BasicCredentialsProvider(accessKey: String, secretKey: String) extends CredentialsProvider {
4 | private val credentials: Credentials = Credentials(accessKey, secretKey)
5 | override def getCredentials: Credentials = credentials
6 | override def refresh: Unit = {}
7 | }
8 |
9 | object BasicCredentialsProvider {
10 | def apply(accessKey: String, secretKey: String): BasicCredentialsProvider =
11 | new BasicCredentialsProvider(accessKey, secretKey)
12 |
13 | def apply(credentials: Credentials): BasicCredentialsProvider =
14 | new BasicCredentialsProvider(credentials.getAWSAccessKeyId, credentials.getAWSSecretKey)
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/Condition.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.auth.{ policy => aws }
5 |
6 | case class Condition(key: String, typeName: String, conditionValues: Seq[String]) extends aws.Condition {
7 | setConditionKey(key)
8 | setType(typeName)
9 | setValues(conditionValues.asJava)
10 |
11 | def specifiedValues: Seq[String] = getValues.asScala.toSeq
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/Credentials.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | class Credentials(accessKeyId: String, secretAccessKey: String) extends com.amazonaws.auth.AWSCredentials {
4 | override def getAWSAccessKeyId: String = accessKeyId
5 | override def getAWSSecretKey: String = secretAccessKey
6 | }
7 |
8 | class SessionCredentials(accessKeyId: String, secretAccessKey: String, token: String)
9 | extends Credentials(accessKeyId, secretAccessKey) with com.amazonaws.auth.AWSSessionCredentials {
10 | override def getSessionToken: String = token
11 | }
12 |
13 | object Credentials {
14 | def apply(accessKeyId: String, secretAccessKey: String): Credentials =
15 | new Credentials(accessKeyId, secretAccessKey)
16 |
17 | def apply(accessKeyId: String, secretAccessKey: String, token: String): Credentials =
18 | new SessionCredentials(accessKeyId, secretAccessKey, token)
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/CredentialsLoader.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import com.amazonaws.auth._
4 | import com.amazonaws.AmazonClientException
5 |
6 | /**
7 | * AWS Credentials loader.
8 | */
9 | object CredentialsLoader {
10 |
11 | def load(): CredentialsProvider = {
12 | val provider = DefaultCredentialsProvider()
13 | if (tryCredentials(provider)) {
14 | provider
15 | } else {
16 | throw new IllegalStateException(s"Failed to load AWS credentials! Make sure about environment or configuration.")
17 | }
18 | }
19 |
20 | private[this] def tryCredentials(provider: CredentialsProvider): Boolean = {
21 | try {
22 | provider.getCredentials
23 | true
24 | } catch {
25 | case e: AmazonClientException => false
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/CredentialsProvider.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | trait CredentialsProvider extends com.amazonaws.auth.AWSCredentialsProvider {
4 | override def getCredentials: Credentials
5 | override def refresh: Unit
6 | }
7 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/DateTime.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import org.joda.time.format.DateTimeFormatter
4 |
5 | object DateTime {
6 | import org.joda.time.{ DateTime => Joda, _ }
7 |
8 | def now() = Joda.now()
9 | def now(zone: DateTimeZone) = Joda.now(zone)
10 | def now(chronology: Chronology) = Joda.now(chronology)
11 |
12 | def parse(str: String) = Joda.parse(str)
13 | def parse(str: String, formatter: DateTimeFormatter) = Joda.parse(str, formatter)
14 |
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/DefaultCredentialsProvider.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | class DefaultCredentialsProvider extends CredentialsProvider {
4 | private val provider = new com.amazonaws.auth.DefaultAWSCredentialsProviderChain
5 | override def getCredentials: Credentials = {
6 | provider.getCredentials match {
7 | case sc: com.amazonaws.auth.AWSSessionCredentials => Credentials(sc.getAWSAccessKeyId, sc.getAWSSecretKey, sc.getSessionToken)
8 | case c => Credentials(c.getAWSAccessKeyId, c.getAWSSecretKey)
9 | }
10 | }
11 | override def refresh: Unit = provider.refresh
12 | }
13 |
14 | object DefaultCredentialsProvider {
15 | def apply(): DefaultCredentialsProvider =
16 | new DefaultCredentialsProvider
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/Effect.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import com.amazonaws.auth.{ policy => aws }
4 |
5 | object Effect {
6 | val Allow = aws.Statement.Effect.Allow
7 | val Deny = aws.Statement.Effect.Deny
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/Policy.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.auth.{ policy => aws }
5 |
6 | case class Policy(statements: Seq[Statement], id: Option[String] = None) extends aws.Policy {
7 | id.foreach(i => setId(i))
8 | setStatements(statements.map(_.asInstanceOf[aws.Statement]).asJava)
9 |
10 | def version = getVersion
11 |
12 | def toJSON = toJson
13 | def asJson = toJson
14 | def asJSON = toJson
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/Region0.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | object Region0 {
4 |
5 | import com.amazonaws.{ regions => awsregions }
6 |
7 | private[this] var defaultRegion = awsregions.Region.getRegion(awsregions.Regions.DEFAULT_REGION)
8 |
9 | def default(): Region = defaultRegion
10 | def default(region: Region): Unit = defaultRegion = region
11 |
12 | def apply(name: String): Region = {
13 | try {
14 | apply(awsregions.Regions.fromName(name))
15 | } catch {
16 | case _: IllegalArgumentException => null
17 | }
18 | }
19 |
20 | def apply(name: awsregions.Regions): Region = awsregions.Region.getRegion(name)
21 |
22 | val AP_NORTHEAST_1 = apply(awsregions.Regions.AP_NORTHEAST_1)
23 | val Tokyo = AP_NORTHEAST_1
24 |
25 | val AP_NORTHEAST_2 = apply(awsregions.Regions.AP_NORTHEAST_2)
26 | val Seoul = AP_NORTHEAST_2
27 |
28 | val AP_SOUTHEAST_1 = apply(awsregions.Regions.AP_SOUTHEAST_1)
29 | val Singapore = AP_SOUTHEAST_1
30 |
31 | val AP_SOUTHEAST_2 = apply(awsregions.Regions.AP_SOUTHEAST_2)
32 | val Sydney = AP_SOUTHEAST_2
33 |
34 | val AP_SOUTH_1 = apply(awsregions.Regions.AP_SOUTH_1)
35 | val Mumbai = AP_SOUTH_1
36 |
37 | val CN_NORTH_1 = apply(awsregions.Regions.CN_NORTH_1)
38 | val Beijing = CN_NORTH_1
39 |
40 | // Use string literal to avoid NoSuchFieldError on older AWS SDK versions.
41 | // Region introduced in AWS SDK for Java 1.11.247:
42 | // https://github.com/aws/aws-sdk-java/commit/440577a61505f8b0d831106745f8584c007b9cd6
43 | val CN_NORTHWEST_1 = apply("cn-northwest-1")
44 | val Ningxia = CN_NORTHWEST_1
45 |
46 | val EU_CENTRAL_1 = apply(awsregions.Regions.EU_CENTRAL_1)
47 | val Frankfurt = EU_CENTRAL_1
48 |
49 | val CA_CENTRAL_1 = apply(awsregions.Regions.CA_CENTRAL_1)
50 | val Canada = CA_CENTRAL_1
51 |
52 | val EU_WEST_1 = apply(awsregions.Regions.EU_WEST_1)
53 | val Ireland = EU_WEST_1
54 |
55 | val EU_WEST_2 = apply(awsregions.Regions.EU_WEST_2)
56 | val London = EU_WEST_2
57 |
58 | // Use string literal to avoid NoSuchFieldError on older AWS SDK versions.
59 | // Introduced in AWS SDK for Java 1.11.251:
60 | // https://github.com/aws/aws-sdk-java/commit/39ebf439b8e4050684cbfca1811c84b3ac5f2468
61 | val EU_WEST_3 = apply("eu-west-3")
62 | val Paris = EU_WEST_3
63 |
64 | val EU_NORTH_1 = apply("eu-north-1")
65 | val Stockholm = EU_NORTH_1
66 |
67 | val GovCloud = apply(awsregions.Regions.GovCloud)
68 | val USGovWest1 = GovCloud
69 | val USGovEast1 = apply(awsregions.Regions.US_GOV_EAST_1)
70 |
71 | val SA_EAST_1 = apply(awsregions.Regions.SA_EAST_1)
72 | val SaoPaulo = SA_EAST_1
73 |
74 | val US_EAST_1 = apply(awsregions.Regions.US_EAST_1)
75 | val NorthernVirginia = US_EAST_1
76 |
77 | val US_EAST_2 = apply(awsregions.Regions.US_EAST_2)
78 | val Ohio = US_EAST_2
79 |
80 | val US_WEST_1 = apply(awsregions.Regions.US_WEST_1)
81 | val NorthernCalifornia = US_WEST_1
82 |
83 | val US_WEST_2 = apply(awsregions.Regions.US_WEST_2)
84 | val Oregon = US_WEST_2
85 |
86 | lazy val all: Seq[Region] = Seq(
87 | Tokyo,
88 | Seoul,
89 | Singapore,
90 | Sydney,
91 | Mumbai,
92 | Beijing,
93 | Ningxia,
94 | Frankfurt,
95 | Canada,
96 | Ireland,
97 | Stockholm,
98 | London,
99 | Paris,
100 | USGovEast1,
101 | USGovWest1,
102 | SaoPaulo,
103 | NorthernVirginia,
104 | Ohio,
105 | NorthernCalifornia,
106 | Oregon)
107 | .filter(_ != null) // Remove null regions in case of older AWS SDK version.
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/Resource.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import com.amazonaws.auth.{ policy => aws }
4 |
5 | case class Resource(id: String) extends aws.Resource(id)
6 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/Sequencer.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import scala.jdk.CollectionConverters._
4 |
5 | /**
6 | * Sequencer is a trait that provides a method to returns a Stream over a list of items from AWS
7 | * that are normally returned in chunks by repeated calls to the AWS APIs.
8 | *
9 | * @tparam Item is the type of the item in the sequence
10 | * @tparam Result is the type of the AWS *Result object
11 | * @tparam Marker is the type of the continuation token returned by AWS, usually String
12 | */
13 | trait Sequencer[Item, Result, Marker] {
14 | def getInitial: Result
15 | def getMarker(r: Result): Marker
16 | def getFromMarker(marker: Marker): Result
17 | def getList(r: Result): java.util.List[Item]
18 |
19 | def sequence: Seq[Item] = {
20 | case class State[Item](items: List[Item], nextMarker: Option[Marker])
21 |
22 | @scala.annotation.tailrec
23 | def next(state: State[Item]): (Option[Item], State[Item]) = state match {
24 | case State(head :: tail, nextMarker) => (Some(head), State(tail, nextMarker))
25 | case State(Nil, Some(nextMarker)) =>
26 | val result = getFromMarker(nextMarker)
27 | next(State[Item](getList(result).asScala.toList, Option(getMarker(result))))
28 | case State(Nil, None) => (None, state)
29 | }
30 |
31 | def toStream(state: State[Item]): Stream[Item] =
32 | next(state) match {
33 | case (Some(item), nextState) => Stream.cons(item, toStream(nextState))
34 | case (None, _) => Stream.Empty
35 | }
36 |
37 | val result = getInitial
38 | toStream(State(getList(result).asScala.toList, Option(getMarker(result))))
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/Statement.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.auth.{ policy => aws }
5 |
6 | case class Statement(
7 | effect: aws.Statement.Effect,
8 | actions: Seq[Action],
9 | resources: Seq[Resource],
10 | id: Option[String] = None,
11 | conditions: Seq[Condition] = Nil,
12 | principals: Seq[aws.Principal] = Nil) extends aws.Statement(effect) {
13 |
14 | id.foreach(i => setId(i))
15 | setEffect(effect)
16 | setActions(actions.map(_.asInstanceOf[aws.Action]).asJava)
17 | setConditions(conditions.map(_.asInstanceOf[aws.Condition]).asJava)
18 | setPrincipals(principals.asJava)
19 | setResources(resources.map(_.asInstanceOf[aws.Resource]).asJava)
20 | }
21 |
--------------------------------------------------------------------------------
/core/src/main/scala/awscala/package.scala:
--------------------------------------------------------------------------------
1 | package object awscala {
2 | // Workaround for https://issues.scala-lang.org/browse/SI-7139
3 | val Region: Region0.type = Region0
4 | type Region = com.amazonaws.regions.Region
5 | type DateTime = org.joda.time.DateTime
6 | type ByteBuffer = java.nio.ByteBuffer
7 | type File = java.io.File
8 |
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/logback.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | logs/console.log
5 |
6 | %date %level [%thread] %logger{10} [%file:%line] %msg%n
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/core/src/test/scala/awscala/RegionSpec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import com.amazonaws.regions.{ Regions => AwsRegions }
4 | import org.scalatest.flatspec.AnyFlatSpec
5 | import org.scalatest.matchers.should.Matchers
6 |
7 | class RegionSpec extends AnyFlatSpec with Matchers {
8 |
9 | behavior of "Region0"
10 |
11 | it should "have all of the regions defined" in {
12 | val regionsFromAws = AwsRegions.values().map(_.getName.toUpperCase).sorted
13 |
14 | val regionsFromAwsScala = Region0.all.toArray.map(_.getName.toUpperCase).sorted
15 |
16 | regionsFromAwsScala.diff(regionsFromAws) shouldBe empty
17 | regionsFromAws.diff(regionsFromAwsScala) shouldBe empty
18 |
19 | regionsFromAwsScala should contain theSameElementsAs regionsFromAws
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala-2/awscala/dynamodbv2/TableCompat.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import DynamoDB.SimplePk
4 |
5 | import scala.reflect.runtime.{ universe => u }
6 | import scala.reflect.runtime.universe.termNames
7 |
8 | import scala.collection.mutable.ListBuffer
9 | import scala.annotation.StaticAnnotation
10 |
11 | class hashPK extends StaticAnnotation
12 | class rangePK extends StaticAnnotation
13 |
14 | private[dynamodbv2] trait TableCompat { self: Table =>
15 |
16 | def putItem[T: u.TypeTag](entity: T)(implicit dynamoDB: DynamoDB): Unit = {
17 | val constructorArgs: Seq[AnnotatedConstructorArgMeta] = extractAnnotatedConstructorArgs(entity)
18 | val getterCallResults: Seq[(String, AnyRef)] = extractGetterNameAndValue(entity)
19 |
20 | var maybeHashPK: Option[Any] = None
21 | var maybeRangePK: Option[Any] = None
22 | val attributes: ListBuffer[(String, AnyRef)] = ListBuffer()
23 | for (nameAndValue <- getterCallResults) {
24 | val (name, value) = nameAndValue
25 | constructorArgs.find { arg => name == arg.name } match {
26 | case Some(arg) if arg.annotationNames.exists(_.contains("hashPK")) => maybeHashPK = Some(value)
27 | case Some(arg) if arg.annotationNames.exists(_.contains("rangePK")) => maybeRangePK = Some(value)
28 | case _ => attributes += nameAndValue
29 | }
30 | }
31 | (maybeHashPK, maybeRangePK) match {
32 | case (Some(hashPK), Some(rangePK)) =>
33 | dynamoDB.put(this, hashPK, rangePK, attributes.toSeq: _*)
34 | case (Some(hashPK), None) =>
35 | dynamoDB.put(this, hashPK, attributes.toSeq: _*)
36 | case _ =>
37 | throw new Exception(s"Primary key is not defined for ${entity.getClass.getName} (constructor args are $constructorArgs)")
38 | }
39 | }
40 |
41 | def putItem(hashPK: Any, attributes: SimplePk*)(implicit dynamoDB: DynamoDB): Unit = {
42 | dynamoDB.put(this, hashPK, attributes: _*)
43 | }
44 | def putItem(hashPK: Any, rangePK: Any, attributes: SimplePk*)(implicit dynamoDB: DynamoDB): Unit = {
45 | dynamoDB.put(this, hashPK, rangePK, attributes: _*)
46 | }
47 |
48 | private case class AnnotatedConstructorArgMeta(name: String, annotationNames: Seq[String])
49 |
50 | private def extractAnnotatedConstructorArgs[T: u.TypeTag](entity: T): Seq[AnnotatedConstructorArgMeta] = {
51 | u.typeOf[entity.type].decl(termNames.CONSTRUCTOR).asMethod.paramLists.flatten
52 | .collect({
53 | case t if t != null && t.annotations.nonEmpty =>
54 | Some(AnnotatedConstructorArgMeta(
55 | name = t.name.toString,
56 | // FIXME: should we use canonical name?
57 | annotationNames = t.annotations.map(_.toString)))
58 | case _ => None
59 | }).flatten
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala-3/awscala/dynamodbv2/TableCompat.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import DynamoDB.SimplePk
4 |
5 | private[dynamodbv2] trait TableCompat { self: Table =>
6 | def putItem(hashPK: Any, attributes: SimplePk*)(implicit dynamoDB: DynamoDB): Unit = {
7 | dynamoDB.put(this, hashPK, attributes: _*)
8 | }
9 | def putItem(hashPK: Any, rangePK: Any, attributes: SimplePk*)(implicit dynamoDB: DynamoDB): Unit = {
10 | dynamoDB.put(this, hashPK, rangePK, attributes: _*)
11 | }
12 | }
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/Attribute.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | case class Attribute(name: String, value: AttributeValue)
4 |
5 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/AttributeAction.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | object AttributeAction {
6 |
7 | val Add = aws.model.AttributeAction.ADD
8 | val Put = aws.model.AttributeAction.PUT
9 | val Delete = aws.model.AttributeAction.DELETE
10 |
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/AttributeDefinition.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | object AttributeDefinition {
6 | def apply(a: aws.model.AttributeDefinition): AttributeDefinition = new AttributeDefinition(
7 | name = a.getAttributeName,
8 | scalarType = aws.model.ScalarAttributeType.fromValue(a.getAttributeType))
9 | }
10 |
11 | case class AttributeDefinition(name: String, scalarType: aws.model.ScalarAttributeType) extends aws.model.AttributeDefinition {
12 | setAttributeName(name)
13 | setAttributeType(scalarType)
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/AttributeType.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType
4 |
5 | object AttributeType {
6 |
7 | val String = ScalarAttributeType.S
8 | val Number = ScalarAttributeType.N
9 | val Binary = ScalarAttributeType.B
10 |
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/AttributeValue.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.services.{ dynamodbv2 => aws }
6 | import java.util.{ Map => JMap }
7 |
8 | object AttributeValue {
9 |
10 | private def recurseMapValue(valueMap: Map[String, Any]): Map[String, aws.model.AttributeValue] = valueMap.map {
11 | case (key, xs: Seq[_]) => key -> toJavaValue(xs)
12 | case (key, vl: Map[_, _]) => key -> {
13 | val _vl: Map[String, Any] = vl.map { case (k, v) => k.asInstanceOf[String] -> v }
14 | new aws.model.AttributeValue().withM(recurseMapValue(_vl).asJava)
15 | }
16 | case (key: String, vl: Object) => key -> toJavaValue(vl)
17 | }
18 |
19 | def toJavaValue(v: Any): aws.model.AttributeValue = {
20 | val attributeValue = new aws.model.AttributeValue
21 | v match {
22 | case null => null
23 | case s: String => attributeValue.withS(s)
24 | case bl: Boolean => attributeValue.withBOOL(bl)
25 | case n: java.lang.Number => attributeValue.withN(n.toString)
26 | case b: ByteBuffer => attributeValue.withB(b)
27 | case xs: Seq[_] => xs.headOption match {
28 | case Some(_: Map[_, _]) => attributeValue.withL(xs.map(toJavaValue).asJavaCollection)
29 | case Some(_: String) => attributeValue.withSS(xs.map(_.asInstanceOf[String]).asJava)
30 | case Some(_: java.lang.Number) => attributeValue.withNS(xs.map(_.toString).asJava)
31 | case Some(_: ByteBuffer) => attributeValue.withBS(xs.map(_.asInstanceOf[ByteBuffer]).asJava)
32 | case Some(_) => attributeValue.withSS(xs.map(_.toString).asJava)
33 | case _ => null
34 | }
35 | case m: Map[_, _] =>
36 | attributeValue.withM(recurseMapValue(m.map { case (k, value) => k.asInstanceOf[String] -> value }).asJava)
37 | case _ => null
38 | }
39 | }
40 |
41 | def apply(v: aws.model.AttributeValue): AttributeValue = new AttributeValue(
42 | s = Option(v.getS),
43 | bl = Option[java.lang.Boolean](v.getBOOL).map(_.booleanValue()),
44 | n = Option(v.getN),
45 | b = Option(v.getB),
46 | m = Option(v.getM),
47 | l = Option(v.getL).map(_.asScala).getOrElse(Nil).toSeq,
48 | ss = Option(v.getSS).map(_.asScala).getOrElse(Nil).toSeq,
49 | ns = Option(v.getNS).map(_.asScala).getOrElse(Nil).toSeq,
50 | bs = Option(v.getBS).map(_.asScala).getOrElse(Nil).toSeq)
51 | }
52 |
53 | case class AttributeValue(
54 | s: Option[String] = None,
55 | bl: Option[Boolean] = None,
56 | n: Option[String] = None,
57 | b: Option[ByteBuffer] = None,
58 | m: Option[JMap[String, aws.model.AttributeValue]] = None,
59 | l: Seq[aws.model.AttributeValue] = Nil,
60 | ss: Seq[String] = Nil,
61 | ns: Seq[String] = Nil,
62 | bs: Seq[ByteBuffer] = Nil) extends aws.model.AttributeValue {
63 |
64 | setS(s.orNull[String])
65 | bl.foreach(setBOOL(_))
66 | setN(n.orNull[String])
67 | setB(b.orNull[ByteBuffer])
68 | setM(m.orNull[JMap[String, aws.model.AttributeValue]])
69 | setL(l.asJavaCollection)
70 | setSS(ss.asJava)
71 | setNS(ns.asJava)
72 | setBS(bs.asJava)
73 | }
74 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/BillingMode.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | object BillingMode {
6 |
7 | val Provisioned = aws.model.BillingMode.PROVISIONED
8 | val PayPerRequest = aws.model.BillingMode.PAY_PER_REQUEST
9 |
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/DynamoDBCondition.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.dynamodbv2.model.{ Condition, ExpectedAttributeValue }
4 |
5 | import scala.jdk.CollectionConverters._
6 | import com.amazonaws.services.{ dynamodbv2 => aws }
7 |
8 | trait DynamoCompares[A] {
9 |
10 | def value: A
11 |
12 | def withComparisonOperator(o: aws.model.ComparisonOperator): DynamoCompares[A]
13 | def withAttributeValueList(vs: java.util.Collection[aws.model.AttributeValue]): DynamoCompares[A]
14 | }
15 |
16 | class EACompares(ea: aws.model.ExpectedAttributeValue) extends DynamoCompares[aws.model.ExpectedAttributeValue] {
17 | val value: ExpectedAttributeValue = ea
18 | def withComparisonOperator(o: aws.model.ComparisonOperator) =
19 | new EACompares(ea.withComparisonOperator(o))
20 | def withAttributeValueList(vs: java.util.Collection[aws.model.AttributeValue]) =
21 | new EACompares(ea.withAttributeValueList(vs))
22 | }
23 |
24 | class CondCompares(c: aws.model.Condition) extends DynamoCompares[aws.model.Condition] {
25 | val value: Condition = c
26 | def withComparisonOperator(o: aws.model.ComparisonOperator) =
27 | new CondCompares(c.withComparisonOperator(o))
28 | def withAttributeValueList(vs: java.util.Collection[aws.model.AttributeValue]) =
29 | new CondCompares(c.withAttributeValueList(vs))
30 | }
31 |
32 | trait DynamoConditions[A] {
33 | def cond: DynamoCompares[A]
34 |
35 | def eq(values: Any*): A = cond
36 | .withComparisonOperator(aws.model.ComparisonOperator.EQ)
37 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
38 | .value
39 |
40 | def ne(values: Any*): A = cond
41 | .withComparisonOperator(aws.model.ComparisonOperator.NE)
42 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
43 | .value
44 |
45 | def gt(values: Any*): A = cond
46 | .withComparisonOperator(aws.model.ComparisonOperator.GT)
47 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
48 | .value
49 |
50 | def ge(values: Any*): A = cond
51 | .withComparisonOperator(aws.model.ComparisonOperator.GE)
52 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
53 | .value
54 |
55 | def lt(values: Any*): A = cond
56 | .withComparisonOperator(aws.model.ComparisonOperator.LT)
57 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
58 | .value
59 |
60 | def le(values: Any*): A = cond
61 | .withComparisonOperator(aws.model.ComparisonOperator.LE)
62 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
63 | .value
64 |
65 | def in(values: Any*): A = cond
66 | .withComparisonOperator(aws.model.ComparisonOperator.IN)
67 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
68 | .value
69 |
70 | def between(values: Any*): A = cond
71 | .withComparisonOperator(aws.model.ComparisonOperator.BETWEEN)
72 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
73 | .value
74 |
75 | def isNotNull: A = cond
76 | .withComparisonOperator(aws.model.ComparisonOperator.NOT_NULL)
77 | .value
78 |
79 | def isNull: A = cond
80 | .withComparisonOperator(aws.model.ComparisonOperator.NULL)
81 | .value
82 |
83 | def contains(values: Any*): A = cond
84 | .withComparisonOperator(aws.model.ComparisonOperator.CONTAINS)
85 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
86 | .value
87 |
88 | def notContains(values: Any*): A = cond
89 | .withComparisonOperator(aws.model.ComparisonOperator.NOT_CONTAINS)
90 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
91 | .value
92 |
93 | def beginsWith(values: Any*): A = cond
94 | .withComparisonOperator(aws.model.ComparisonOperator.BEGINS_WITH)
95 | .withAttributeValueList(values.map(v => AttributeValue.toJavaValue(v)).asJava)
96 | .value
97 |
98 | }
99 |
100 | object DynamoDBCondition extends DynamoConditions[aws.model.Condition] {
101 | def cond = new CondCompares(new aws.model.Condition)
102 | }
103 |
104 | object DynamoDBExpectedAttributeValue extends DynamoConditions[aws.model.ExpectedAttributeValue] {
105 | def cond = new EACompares(new aws.model.ExpectedAttributeValue)
106 | }
107 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/DynamoDBImplicits.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | trait DynamoDBImplicits {
6 | implicit class RichScalaAttributeValue(v: AttributeValue) {
7 | def asJava: aws.model.AttributeValue = AttributeValue.toJavaValue(v)
8 | }
9 |
10 | implicit class RichJavaAttributeValue(v: aws.model.AttributeValue) {
11 | def asScala: AttributeValue = AttributeValue(v)
12 | }
13 | }
14 |
15 | object DynamoDBImplicits extends DynamoDBImplicits
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/GlobalSecondaryIndex.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.services.{ dynamodbv2 => aws }
5 |
6 | object GlobalSecondaryIndex {
7 |
8 | def apply(v: aws.model.GlobalSecondaryIndexDescription): GlobalSecondaryIndex = GlobalSecondaryIndex(
9 | name = v.getIndexName,
10 | keySchema = v.getKeySchema.asScala.map(k => KeySchema(k)).toSeq,
11 | projection = Projection(v.getProjection),
12 | provisionedThroughput =
13 | Option(v.getProvisionedThroughput)
14 | .map { pt => ProvisionedThroughput(pt.getReadCapacityUnits, pt.getWriteCapacityUnits) })
15 |
16 | def apply(
17 | name: String,
18 | keySchema: Seq[KeySchema],
19 | projection: Projection,
20 | provisionedThroughput: ProvisionedThroughput): GlobalSecondaryIndex =
21 | new GlobalSecondaryIndex(name, keySchema, projection, Option(provisionedThroughput))
22 | }
23 |
24 | case class GlobalSecondaryIndex(
25 | name: String,
26 | keySchema: Seq[KeySchema],
27 | projection: Projection,
28 | provisionedThroughput: Option[ProvisionedThroughput] = None) extends aws.model.GlobalSecondaryIndex with SecondaryIndex {
29 |
30 | setIndexName(name)
31 | setKeySchema(keySchema.map(_.asInstanceOf[aws.model.KeySchemaElement]).asJava)
32 | setProjection(projection)
33 | provisionedThroughput.foreach(setProvisionedThroughput)
34 | }
35 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/Item.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.services.{ dynamodbv2 => aws }
5 |
6 | object Item {
7 | def apply(table: Table, attributes: java.util.Map[String, aws.model.AttributeValue]): Item = new Item(
8 | table = table,
9 | attributes = attributes.asScala.toSeq.map { case (k, v) => Attribute(k, AttributeValue(v)) })
10 | }
11 |
12 | case class Item(table: Table, attributes: Seq[Attribute])
13 |
14 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/KeySchema.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | object KeySchema {
6 | def apply(k: aws.model.KeySchemaElement): KeySchema = new KeySchema(
7 | attributeName = k.getAttributeName,
8 | keyType = aws.model.KeyType.fromValue(k.getKeyType))
9 | }
10 |
11 | case class KeySchema(attributeName: String, keyType: KeyType) extends aws.model.KeySchemaElement {
12 | setAttributeName(attributeName)
13 | setKeyType(keyType)
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/KeyType.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | object KeyType {
6 |
7 | val Hash = aws.model.KeyType.HASH
8 | val Range = aws.model.KeyType.RANGE
9 |
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/LocalSecondaryIndex.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.services.{ dynamodbv2 => aws }
5 |
6 | object LocalSecondaryIndex {
7 |
8 | def apply(v: aws.model.LocalSecondaryIndexDescription): LocalSecondaryIndex = new LocalSecondaryIndex(
9 | name = v.getIndexName,
10 | keySchema = v.getKeySchema.asScala.map(k => KeySchema(k)).toSeq,
11 | projection = Projection(v.getProjection))
12 | }
13 |
14 | case class LocalSecondaryIndex(
15 | name: String,
16 | keySchema: Seq[KeySchema],
17 | projection: Projection) extends aws.model.LocalSecondaryIndex with SecondaryIndex {
18 |
19 | setIndexName(name)
20 | setKeySchema(keySchema.map(_.asInstanceOf[aws.model.KeySchemaElement]).asJava)
21 | setProjection(projection)
22 | }
23 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/PageStats.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | case class PageStats(page: Int, lastPage: Boolean, limit: Int, scanned: Int, items: Int, consumedCapacity: aws.model.ConsumedCapacity)
6 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/Projection.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.services.{ dynamodbv2 => aws }
5 |
6 | object Projection {
7 | def apply(p: aws.model.Projection): Projection = new Projection(
8 | nonKeyAttributes = Option(p.getNonKeyAttributes).map(_.asScala).getOrElse(Nil).toSeq,
9 | projectionType = aws.model.ProjectionType.fromValue(p.getProjectionType))
10 | }
11 |
12 | case class Projection(projectionType: ProjectionType, nonKeyAttributes: Seq[String] = Nil) extends aws.model.Projection {
13 | setProjectionType(projectionType)
14 |
15 | if (projectionType == ProjectionType.Include) {
16 | setNonKeyAttributes(nonKeyAttributes.asJava)
17 | } else if (nonKeyAttributes.nonEmpty) {
18 | throw new IllegalArgumentException("You shouldn't specify `nonKeyAttributes` when ProjectionType is other than INCLUDE.")
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/ProjectionType.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | object ProjectionType {
6 |
7 | val All = aws.model.ProjectionType.ALL
8 | val Include = aws.model.ProjectionType.INCLUDE
9 | val KeysOnly = aws.model.ProjectionType.KEYS_ONLY
10 |
11 | }
12 |
13 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/ProvisionedThroughput.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | object ProvisionedThroughput {
6 | def apply(meta: ProvisionedThroughputMeta): ProvisionedThroughput = new ProvisionedThroughput(
7 | readCapacityUnits = meta.readCapacityUnits,
8 | writeCapacityUnits = meta.writeCapacityUnits)
9 | }
10 |
11 | case class ProvisionedThroughput(
12 | readCapacityUnits: Long,
13 | writeCapacityUnits: Long) extends aws.model.ProvisionedThroughput {
14 |
15 | setReadCapacityUnits(readCapacityUnits)
16 | setWriteCapacityUnits(writeCapacityUnits)
17 | }
18 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/ResultPager.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import java.util
4 |
5 | import com.amazonaws.services.dynamodbv2.model
6 | import com.amazonaws.services.dynamodbv2.model.{ QueryRequest, ScanRequest }
7 | import com.amazonaws.services.{ dynamodbv2 => aws }
8 |
9 | import scala.jdk.CollectionConverters._
10 |
11 | /**
12 | * The ResultPager allows iteration over the results from a DynamoDB query/scan as a single stream of items,
13 | * handling the necessary paging details in the background.
14 | *
15 | * DynamoDB paginates the result of query/scan operations. The data returned from a Query or Scan operation is limited
16 | * to 1 MB; this means that if the result set exceeds 1 MB of data, you'll need to perform another Query or Scan
17 | * operation to retrieve the next 1 MB of data. In addition, the limit parameter controls the number of items that you
18 | * want DynamoDB to process in a single request before returning results.
19 | * See http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/QueryAndScan.html#Pagination
20 | *
21 | * Each response from DynamoDB indicates if the processing has reached the end of the dataset, or if another request is
22 | * needed in order to continue scanning where the last request finished.
23 | *
24 | * When the items from a page is exhausted, the ResultPager will issue a new query/scan for the next page of results,
25 | * until processing reaches the end of the dataset, or the client stops iterating over the result (as the return value
26 | * is a Stream[Item])
27 | *
28 | * @tparam TReq
29 | * @tparam TRes
30 | */
31 | sealed trait ResultPager[TReq, TRes] extends Iterator[Item] {
32 | def table: Table
33 | def operation: TReq => TRes
34 | def request: TReq
35 |
36 | var items: Seq[Item] = _
37 | var pageNo = 0
38 | var lastKey: java.util.Map[String, aws.model.AttributeValue] = _
39 | var index = 0
40 | nextPage(request)
41 |
42 | def getItems(result: TRes): java.util.List[java.util.Map[String, aws.model.AttributeValue]]
43 | def invokeCallback(result: TRes): Unit
44 | def getLastEvaluatedKey(result: TRes): java.util.Map[String, aws.model.AttributeValue]
45 | def withExclusiveStartKey(request: TReq, lastKey: java.util.Map[String, aws.model.AttributeValue]): TReq
46 | def getCount(result: TRes): Int
47 |
48 | private def nextPage(request: TReq): Unit = {
49 | val result = operation(request)
50 |
51 | request match {
52 | case req: aws.model.QueryRequest =>
53 | if (req.getSelect == aws.model.Select.COUNT.toString)
54 | items = Seq(Item(table, Seq(Attribute("Count", AttributeValue(new AttributeValue(n = Some(getCount(result).toString)))))))
55 | else {
56 | invokeCallback(result)
57 | items = getItems(result).asScala.map(i => Item(table, i)).toSeq
58 | }
59 | case req: aws.model.ScanRequest =>
60 | if (req.getSelect == aws.model.Select.COUNT.toString)
61 | items = Seq(Item(table, Seq(Attribute("Count", AttributeValue(new AttributeValue(n = Some(getCount(result).toString)))))))
62 | else {
63 | invokeCallback(result)
64 | items = getItems(result).asScala.map(i => Item(table, i)).toSeq
65 | }
66 | }
67 |
68 | lastKey = getLastEvaluatedKey(result)
69 | index = 0
70 | pageNo += 1
71 | }
72 |
73 | override def next(): Item = {
74 | val item: Item = items(index)
75 | index += 1
76 | item
77 | }
78 |
79 | override def hasNext: Boolean = {
80 | if (index < items.size) {
81 | true
82 | } else if (lastKey == null) {
83 | false
84 | } else {
85 | while ({
86 | nextPage(withExclusiveStartKey(request, lastKey))
87 | lastKey != null && items.isEmpty // there are potentially more matching data, but this page didn't contain any
88 | }) {}
89 | items.nonEmpty
90 | }
91 | }
92 | }
93 |
94 | // a pager specialized for query request/results
95 | class QueryResultPager(val table: Table, val operation: aws.model.QueryRequest => aws.model.QueryResult, val request: aws.model.QueryRequest, pageStatsCallback: PageStats => Unit)
96 | extends ResultPager[aws.model.QueryRequest, aws.model.QueryResult] {
97 | override def getItems(result: aws.model.QueryResult): util.List[util.Map[String, model.AttributeValue]] = result.getItems
98 | override def getCount(result: aws.model.QueryResult): Int = result.getCount
99 | override def getLastEvaluatedKey(result: aws.model.QueryResult): util.Map[String, model.AttributeValue] = result.getLastEvaluatedKey
100 | override def withExclusiveStartKey(request: aws.model.QueryRequest, lastKey: java.util.Map[String, aws.model.AttributeValue]): QueryRequest = request.withExclusiveStartKey(lastKey)
101 | override def invokeCallback(result: aws.model.QueryResult): Unit = {
102 | Option(pageStatsCallback).foreach(fun => fun(PageStats(pageNo, result.getLastEvaluatedKey == null, request.getLimit, result.getScannedCount, result.getCount, result.getConsumedCapacity)))
103 | }
104 | }
105 |
106 | // a pager specialized for scan request/results
107 | class ScanResultPager(val table: Table, val operation: aws.model.ScanRequest => aws.model.ScanResult, val request: aws.model.ScanRequest, pageStatsCallback: PageStats => Unit)
108 | extends ResultPager[aws.model.ScanRequest, aws.model.ScanResult] {
109 | override def getItems(result: aws.model.ScanResult): util.List[util.Map[String, model.AttributeValue]] = result.getItems
110 | override def getCount(result: aws.model.ScanResult): Int = result.getCount
111 | override def getLastEvaluatedKey(result: aws.model.ScanResult): util.Map[String, model.AttributeValue] = result.getLastEvaluatedKey
112 | override def withExclusiveStartKey(request: aws.model.ScanRequest, lastKey: java.util.Map[String, aws.model.AttributeValue]): ScanRequest = request.withExclusiveStartKey(lastKey)
113 | override def invokeCallback(result: aws.model.ScanResult): Unit = {
114 | Option(pageStatsCallback).foreach(fun => fun(PageStats(pageNo, result.getLastEvaluatedKey == null, request.getLimit, result.getScannedCount, result.getCount, result.getConsumedCapacity)))
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/SecondaryIndex.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | trait SecondaryIndex {
4 |
5 | val name: String
6 |
7 | }
8 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/Table.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import DynamoDB.{ CompositePk, SimplePk }
4 |
5 | import java.lang.reflect.Modifier
6 | import com.amazonaws.services.{ dynamodbv2 => aws }
7 |
8 | import scala.collection.mutable.ListBuffer
9 |
10 | object Table {
11 |
12 | def apply(
13 | name: String,
14 | hashPK: String,
15 | rangePK: Option[String],
16 | attributes: Seq[AttributeDefinition],
17 | localSecondaryIndexes: Seq[LocalSecondaryIndex],
18 | globalSecondaryIndexes: Seq[GlobalSecondaryIndex],
19 | provisionedThroughput: Option[ProvisionedThroughput],
20 | billingMode: aws.model.BillingMode): Table =
21 | new Table(name, hashPK, rangePK, attributes, localSecondaryIndexes,
22 | globalSecondaryIndexes, provisionedThroughput, Option(billingMode))
23 | }
24 |
25 | case class Table(
26 | name: String,
27 | hashPK: String,
28 | rangePK: Option[String] = None,
29 | attributes: Seq[AttributeDefinition] = Nil,
30 | localSecondaryIndexes: Seq[LocalSecondaryIndex] = Nil,
31 | globalSecondaryIndexes: Seq[GlobalSecondaryIndex] = Nil,
32 | provisionedThroughput: Option[ProvisionedThroughput] = None,
33 | billingMode: Option[aws.model.BillingMode] = None) extends TableCompat {
34 |
35 | // ------------------------------------------
36 | // Items
37 | // ------------------------------------------
38 |
39 | def get(hashPK: Any)(implicit dynamoDB: DynamoDB): Option[Item] = getItem(hashPK)
40 | def get(hashPK: Any, rangePK: Any)(implicit dynamoDB: DynamoDB): Option[Item] = getItem(hashPK, rangePK)
41 |
42 | def getItem(hashPK: Any)(implicit dynamoDB: DynamoDB): Option[Item] = {
43 | dynamoDB.get(this, hashPK)
44 | }
45 | def getItem(hashPK: Any, rangePK: Any)(
46 | implicit
47 | dynamoDB: DynamoDB): Option[Item] = {
48 | dynamoDB.get(this, hashPK, rangePK)
49 | }
50 |
51 | def batchGet(attributes: List[SimplePk])(implicit dynamoDB: DynamoDB): Seq[Item] =
52 | batchGetItems(attributes)
53 |
54 | def batchGet(attributes: List[CompositePk])(implicit dynamoDB: DynamoDB, di: DummyImplicit): Seq[Item] =
55 | batchGetItems(attributes)
56 |
57 | def batchGetItems(attributes: List[SimplePk])(implicit dynamoDB: DynamoDB): Seq[Item] =
58 | dynamoDB.batchGet[SimplePk](Map(this -> attributes))
59 |
60 | def batchGetItems(attributes: List[CompositePk])(implicit dynamoDB: DynamoDB, di: DummyImplicit): Seq[Item] =
61 | dynamoDB.batchGet[CompositePk](Map(this -> attributes))
62 |
63 | def put(hashPK: Any, attributes: SimplePk*)(implicit dynamoDB: DynamoDB): Unit = putItem(hashPK, attributes: _*)
64 | def put(hashPK: Any, rangePK: Any, attributes: SimplePk*)(implicit dynamoDB: DynamoDB): Unit = putItem(hashPK, rangePK, attributes: _*)
65 |
66 | def putItem[E <: AnyRef](hashPK: Any, rangePK: Any, entity: E)(implicit dynamoDB: DynamoDB): Unit = {
67 | val attrs = extractGetterNameAndValue(entity)
68 | dynamoDB.put(this, hashPK, rangePK, attrs: _*)
69 | }
70 |
71 | protected def extractGetterNameAndValue(obj: Any): Seq[(String, AnyRef)] = {
72 | val clazz = obj.getClass
73 | val privateInstanceFieldNames: Seq[String] = clazz.getDeclaredFields
74 | .filter(f =>
75 | Modifier.isPrivate(f.getModifiers)
76 | && !Modifier.isStatic(f.getModifiers))
77 | .toIndexedSeq // To avoid implicit conversion
78 | .map(_.getName)
79 | val getterMethodNames: Seq[String] = clazz.getDeclaredMethods
80 | .filter(m =>
81 | Modifier.isPublic(m.getModifiers)
82 | && !Modifier.isStatic(m.getModifiers)
83 | && m.getParameterTypes.length == 0
84 | && privateInstanceFieldNames.contains(m.getName))
85 | .toIndexedSeq // To avoid implicit conversion
86 | .map(_.getName)
87 |
88 | getterMethodNames.map(name => (name, clazz.getDeclaredMethod(name).invoke(obj)))
89 | }
90 | def delete(hashPK: Any)(implicit dynamoDB: DynamoDB): Unit = deleteItem(hashPK)
91 | def delete(hashPK: Any, rangePK: Any)(implicit dynamoDB: DynamoDB): Unit = deleteItem(hashPK, rangePK)
92 |
93 | def deleteItem(hashPK: Any)(implicit dynamoDB: DynamoDB): Unit = {
94 | dynamoDB.deleteItem(this, hashPK)
95 | }
96 | def deleteItem(hashPK: Any, rangePK: Any)(implicit dynamoDB: DynamoDB): Unit = {
97 | dynamoDB.deleteItem(this, hashPK, rangePK)
98 | }
99 |
100 | def queryWithIndex(
101 | index: SecondaryIndex,
102 | keyConditions: Seq[(String, aws.model.Condition)],
103 | select: Select = aws.model.Select.ALL_ATTRIBUTES,
104 | attributesToGet: Seq[String] = Nil,
105 | scanIndexForward: Boolean = true,
106 | consistentRead: Boolean = false,
107 | limit: Int = 1000,
108 | pageStatsCallback: PageStats => Unit = null)(implicit dynamoDB: DynamoDB): Seq[Item] = {
109 | dynamoDB.queryWithIndex(
110 | table = this,
111 | index = index,
112 | keyConditions = keyConditions,
113 | select = select,
114 | attributesToGet = attributesToGet,
115 | scanIndexForward = scanIndexForward,
116 | consistentRead = consistentRead,
117 | limit = limit,
118 | pageStatsCallback = pageStatsCallback)
119 | }
120 |
121 | def query(
122 | keyConditions: Seq[(String, aws.model.Condition)],
123 | select: Select = aws.model.Select.ALL_ATTRIBUTES,
124 | attributesToGet: Seq[String] = Nil,
125 | scanIndexForward: Boolean = true,
126 | consistentRead: Boolean = false,
127 | limit: Int = 1000,
128 | pageStatsCallback: PageStats => Unit = null)(implicit dynamoDB: DynamoDB): Seq[Item] = {
129 | dynamoDB.query(
130 | table = this,
131 | keyConditions = keyConditions,
132 | select = select,
133 | attributesToGet = attributesToGet,
134 | scanIndexForward = scanIndexForward,
135 | consistentRead = consistentRead,
136 | limit = limit,
137 | pageStatsCallback = pageStatsCallback)
138 | }
139 |
140 | def scan(
141 | filter: Seq[(String, aws.model.Condition)],
142 | select: Select = aws.model.Select.ALL_ATTRIBUTES,
143 | attributesToGet: Seq[String] = Nil,
144 | limit: Int = 1000,
145 | segment: Int = 0,
146 | totalSegments: Int = 1,
147 | consistentRead: Boolean = false,
148 | pageStatsCallback: PageStats => Unit = null)(implicit dynamoDB: DynamoDB): Seq[Item] = {
149 | dynamoDB.scan(
150 | table = this,
151 | filter = filter,
152 | limit = limit,
153 | segment = segment,
154 | totalSegments = totalSegments,
155 | select = select,
156 | attributesToGet = attributesToGet,
157 | consistentRead = consistentRead,
158 | pageStatsCallback = pageStatsCallback)
159 | }
160 |
161 | def addAttributes(hashPK: Any, attributes: SimplePk*)(
162 | implicit
163 | dynamoDB: DynamoDB): Unit = {
164 | dynamoDB.updateAttributes(this, hashPK, None, aws.model.AttributeAction.ADD, attributes)
165 | }
166 | def addAttributes(hashPK: Any, rangePK: Any, attributes: Seq[SimplePk])(
167 | implicit
168 | dynamoDB: DynamoDB): Unit = {
169 | dynamoDB.updateAttributes(this, hashPK, Some(rangePK), aws.model.AttributeAction.ADD, attributes)
170 | }
171 |
172 | def deleteAttributes(hashPK: Any, attributes: Seq[SimplePk])(
173 | implicit
174 | dynamoDB: DynamoDB): Unit = {
175 | dynamoDB.updateAttributes(this, hashPK, None, aws.model.AttributeAction.DELETE, attributes)
176 | }
177 | def deleteAttributes(hashPK: Any, rangePK: Any, attributes: Seq[SimplePk])(
178 | implicit
179 | dynamoDB: DynamoDB): Unit = {
180 | dynamoDB.updateAttributes(this, hashPK, Some(rangePK), aws.model.AttributeAction.DELETE, attributes)
181 | }
182 |
183 | def putAttributes(hashPK: Any, attributes: Seq[SimplePk])(
184 | implicit
185 | dynamoDB: DynamoDB): Unit = {
186 | dynamoDB.updateAttributes(this, hashPK, None, aws.model.AttributeAction.PUT, attributes)
187 | }
188 | def putAttributes(hashPK: Any, rangePK: Any, attributes: Seq[SimplePk])(
189 | implicit
190 | dynamoDB: DynamoDB): Unit = {
191 | dynamoDB.updateAttributes(this, hashPK, Some(rangePK), aws.model.AttributeAction.PUT, attributes)
192 | }
193 |
194 | def update(throughput: ProvisionedThroughput)(implicit dynamoDB: DynamoDB): TableMeta = {
195 | dynamoDB.updateTableProvisionedThroughput(this, throughput)
196 | }
197 |
198 | def destroy()(implicit dynamoDB: DynamoDB): Unit = dynamoDB.delete(this)
199 |
200 | }
201 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/TableMeta.scala:
--------------------------------------------------------------------------------
1 | package awscala.dynamodbv2
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ dynamodbv2 => aws }
5 |
6 | import scala.jdk.CollectionConverters._
7 |
8 | object TableMeta {
9 | def apply(t: aws.model.TableDescription): TableMeta = new TableMeta(
10 | name = t.getTableName,
11 | sizeBytes = t.getTableSizeBytes,
12 | itemCount = t.getItemCount,
13 | status = aws.model.TableStatus.fromValue(t.getTableStatus),
14 | attributes = Option(t.getAttributeDefinitions)
15 | .map(_.asScala.map(a => AttributeDefinition(a)).toSeq)
16 | .getOrElse(Nil),
17 | keySchema = Option(t.getKeySchema)
18 | .map(_.asScala.map(s => KeySchema(s)).toSeq)
19 | .getOrElse(Nil),
20 | globalSecondaryIndexes = Option(t.getGlobalSecondaryIndexes).map(_.asScala.toSeq).getOrElse(Seq.empty),
21 | localSecondaryIndexes = Option(t.getLocalSecondaryIndexes).map { indexes =>
22 | indexes.asScala.map(i => LocalSecondaryIndexMeta(i))
23 | }.getOrElse(Nil).toSeq,
24 | provisionedThroughput = ProvisionedThroughputMeta(t.getProvisionedThroughput),
25 | createdAt = new DateTime(t.getCreationDateTime),
26 | billingModeSummary = Option(t.getBillingModeSummary).map(BillingModeSummary.apply))
27 | }
28 |
29 | case class TableMeta(
30 | name: String,
31 | sizeBytes: Long,
32 | itemCount: Long,
33 | status: TableStatus,
34 | attributes: Seq[AttributeDefinition],
35 | keySchema: Seq[KeySchema],
36 | globalSecondaryIndexes: Seq[aws.model.GlobalSecondaryIndexDescription],
37 | localSecondaryIndexes: Seq[LocalSecondaryIndexMeta],
38 | provisionedThroughput: ProvisionedThroughputMeta,
39 | billingModeSummary: Option[BillingModeSummary],
40 | createdAt: DateTime) extends aws.model.TableDescription {
41 |
42 | def table: Table = Table(
43 | name = name,
44 | hashPK = keySchema.find(_.keyType == aws.model.KeyType.HASH).get.attributeName,
45 | rangePK = keySchema.find(_.keyType == aws.model.KeyType.RANGE).map(_.attributeName),
46 | attributes = attributes,
47 | globalSecondaryIndexes = globalSecondaryIndexes.map(GlobalSecondaryIndex.apply),
48 | localSecondaryIndexes = localSecondaryIndexes.map(e => LocalSecondaryIndex(e)),
49 | provisionedThroughput = Some(ProvisionedThroughput(provisionedThroughput)),
50 | billingMode = billingModeSummary.map(_.billingMode).map(aws.model.BillingMode.fromValue))
51 |
52 | setAttributeDefinitions(attributes.map(_.asInstanceOf[aws.model.AttributeDefinition]).asJava)
53 | setCreationDateTime(createdAt.toDate)
54 | setItemCount(itemCount)
55 | setKeySchema(keySchema.map(_.asInstanceOf[aws.model.KeySchemaElement]).asJava)
56 | setGlobalSecondaryIndexes(globalSecondaryIndexes.asJava)
57 | setLocalSecondaryIndexes(localSecondaryIndexes.map(_.asInstanceOf[aws.model.LocalSecondaryIndexDescription]).asJava)
58 | setProvisionedThroughput(provisionedThroughput)
59 | setTableName(name)
60 | setTableSizeBytes(sizeBytes)
61 | setTableStatus(status)
62 | billingModeSummary.foreach(setBillingModeSummary)
63 | }
64 |
65 | object LocalSecondaryIndexMeta {
66 | def apply(i: aws.model.LocalSecondaryIndexDescription): LocalSecondaryIndexMeta = new LocalSecondaryIndexMeta(
67 | name = i.getIndexName,
68 | sizeBytes = i.getIndexSizeBytes,
69 | itemCount = i.getItemCount,
70 | keySchema = i.getKeySchema.asScala.toSeq.map(k => KeySchema(k)),
71 | projection = Projection(i.getProjection))
72 | }
73 | case class LocalSecondaryIndexMeta(
74 | name: String,
75 | sizeBytes: Long,
76 | itemCount: Long,
77 | keySchema: Seq[KeySchema],
78 | projection: Projection) extends aws.model.LocalSecondaryIndexDescription {
79 |
80 | setIndexName(name)
81 | setIndexSizeBytes(sizeBytes)
82 | setItemCount(itemCount)
83 | setKeySchema(keySchema.map(_.asInstanceOf[aws.model.KeySchemaElement]).asJava)
84 | setProjection(projection)
85 | }
86 |
87 | object ProvisionedThroughputMeta {
88 | def apply(p: aws.model.ProvisionedThroughputDescription): ProvisionedThroughputMeta = new ProvisionedThroughputMeta(
89 | numberOfDecreasesToday = p.getNumberOfDecreasesToday,
90 | readCapacityUnits = p.getReadCapacityUnits,
91 | writeCapacityUnits = p.getWriteCapacityUnits,
92 | lastDecreasedAt = new DateTime(p.getLastDecreaseDateTime),
93 | lastIncreasedAt = new DateTime(p.getLastIncreaseDateTime))
94 | }
95 | case class ProvisionedThroughputMeta(
96 | numberOfDecreasesToday: Long,
97 | readCapacityUnits: Long,
98 | writeCapacityUnits: Long,
99 | lastDecreasedAt: DateTime,
100 | lastIncreasedAt: DateTime) extends aws.model.ProvisionedThroughputDescription {
101 |
102 | setLastDecreaseDateTime(lastDecreasedAt.toDate)
103 | setLastIncreaseDateTime(lastIncreasedAt.toDate)
104 | setNumberOfDecreasesToday(numberOfDecreasesToday)
105 | setReadCapacityUnits(readCapacityUnits)
106 | setWriteCapacityUnits(writeCapacityUnits)
107 | }
108 |
109 | object BillingModeSummary {
110 | def apply(p: aws.model.BillingModeSummary): BillingModeSummary = new BillingModeSummary(
111 | billingMode = p.getBillingMode,
112 | lastUpdateToPayPerRequestDateTime = new DateTime(p.getLastUpdateToPayPerRequestDateTime))
113 | }
114 | case class BillingModeSummary(
115 | billingMode: String,
116 | lastUpdateToPayPerRequestDateTime: DateTime) extends aws.model.BillingModeSummary {
117 |
118 | setBillingMode(billingMode)
119 | setLastUpdateToPayPerRequestDateTime(lastUpdateToPayPerRequestDateTime.toDate)
120 | }
121 |
--------------------------------------------------------------------------------
/dynamodb/src/main/scala/awscala/dynamodbv2/package.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import com.amazonaws.services.{ dynamodbv2 => aws }
4 |
5 | package object dynamodbv2 extends DynamoDBImplicits {
6 |
7 | type TableStatus = aws.model.TableStatus
8 | type KeyType = aws.model.KeyType
9 | type AttributeAction = aws.model.AttributeAction
10 | type ProjectionType = aws.model.ProjectionType
11 | type ReturnConsumedCapacity = aws.model.ReturnConsumedCapacity
12 |
13 | type ComparisonOperator = aws.model.ComparisonOperator
14 | type Select = aws.model.Select
15 |
16 | val cond: DynamoDBCondition.type = awscala.dynamodbv2.DynamoDBCondition
17 |
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/dynamodb/src/test/scala-2/awscala/DynamoDBV2AnnotationsSpec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import java.util.Date
4 |
5 | import awscala.dynamodbv2._
6 | import com.amazonaws.services.dynamodbv2.model.{ ProvisionedThroughputDescription, TableDescription, TableStatus }
7 | import com.amazonaws.services.dynamodbv2.util.TableUtils
8 | import com.amazonaws.services.{ dynamodbv2 => aws }
9 | import org.scalatest._
10 | import org.slf4j._
11 |
12 | import scala.util.Try
13 | import org.scalatest.flatspec.AnyFlatSpec
14 | import org.scalatest.matchers.should.Matchers
15 | class DynamoDBV2AnnotationsSpec extends AnyFlatSpec with Matchers {
16 |
17 | behavior of "DynamoDB"
18 |
19 | val log: Logger = LoggerFactory.getLogger(this.getClass)
20 |
21 | case class TestMember(
22 | @hashPK val id: Int,
23 | @rangePK val Country: String,
24 | val Company: String,
25 | val Name: String,
26 | val Age: Int)
27 | it should "allows using case class with annotation in put method" in {
28 | implicit val dynamoDB: DynamoDB = DynamoDB.local()
29 | val tableName = s"Members_${System.currentTimeMillis}"
30 | val createdTableMeta: TableMeta = dynamoDB.createTable(
31 | name = tableName,
32 | hashPK = "Id" -> AttributeType.Number,
33 | rangePK = "Country" -> AttributeType.String,
34 | otherAttributes = Seq(
35 | "Company" -> AttributeType.String),
36 | indexes = Seq(
37 | LocalSecondaryIndex(
38 | name = "CompanyIndex",
39 | keySchema = Seq(KeySchema("Id", KeyType.Hash), KeySchema("Company", KeyType.Range)),
40 | projection = Projection(ProjectionType.Include, Seq("Company")))))
41 | log.info(s"Created Table: $createdTableMeta")
42 |
43 | println(s"Waiting for DynamoDB table activation...")
44 | TableUtils.waitUntilActive(dynamoDB, createdTableMeta.name)
45 | println("")
46 | println(s"Created DynamoDB table has been activated.")
47 |
48 | val members: Table = dynamoDB.table(tableName).get
49 | val member = TestMember(1, "PL", "DataMass", "Alex", 29)
50 | println(members)
51 | members.putItem(member)
52 |
53 | println(members)
54 | println(members.get(1, "PL"))
55 | println(members.get(1, "PL").get.attributes.find(_.name == "Name").get.value)
56 |
57 | members.get(1, "PL").get.attributes.find(_.name == "Name").get.value.s.get should equal("Alex")
58 | members.get(1, "PL").get.attributes.find(_.name == "Company").get.value.s.get should equal("DataMass")
59 | members.get(1, "PL").get.attributes.find(_.name == "Country").get.value.s.get should equal("PL")
60 | members.destroy()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/ec2/src/main/scala/awscala/ec2/EC2.scala:
--------------------------------------------------------------------------------
1 | package awscala.ec2
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.auth.AWSCredentialsProvider
6 | import com.amazonaws.services.{ ec2 => aws }
7 | import scala.annotation.tailrec
8 |
9 | object EC2 {
10 |
11 | def apply(credentials: Credentials)(implicit region: Region): EC2 = new EC2Client(BasicCredentialsProvider(credentials.getAWSAccessKeyId, credentials.getAWSSecretKey)).at(region)
12 | def apply(credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load())(implicit region: Region = Region.default()): EC2 = new EC2Client(credentialsProvider).at(region)
13 | def apply(accessKeyId: String, secretAccessKey: String)(implicit region: Region): EC2 = apply(BasicCredentialsProvider(accessKeyId, secretAccessKey)).at(region)
14 |
15 | def at(region: Region): EC2 = apply()(region)
16 | }
17 |
18 | /**
19 | * Amazon EC2 Java client wrapper
20 | * @see [[http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/]]
21 | */
22 | trait EC2 extends aws.AmazonEC2Async {
23 |
24 | def at(region: Region): EC2 = {
25 | this.setRegion(region)
26 | this
27 | }
28 |
29 | // ------------------------------------------
30 | // Instances
31 | // ------------------------------------------
32 |
33 | def instances: Seq[Instance] = {
34 | describeInstances.getReservations.asScala.flatMap(_.getInstances.asScala.toSeq.map(Instance(_))).toSeq
35 | }
36 |
37 | def instances(instanceId: String*): Seq[Instance] = {
38 | describeInstances(new aws.model.DescribeInstancesRequest().withInstanceIds(instanceId: _*))
39 | .getReservations.asScala.flatMap(_.getInstances.asScala).map(Instance(_)).toSeq
40 | }
41 |
42 | def instances(instanceIds: Seq[String] = Nil, filters: Seq[aws.model.Filter] = Nil): Seq[Instance] = {
43 | describeInstances(new aws.model.DescribeInstancesRequest().withInstanceIds(instanceIds.asJava).withFilters(filters.asJava))
44 | .getReservations.asScala.flatMap(_.getInstances.asScala).map(Instance(_)).toSeq
45 | }
46 |
47 | def runAndAwait(
48 | imageId: String,
49 | keyPair: KeyPair,
50 | instanceType: InstanceType = InstanceType.T1_Micro,
51 | min: Int = 1,
52 | max: Int = 1): Seq[Instance] = {
53 |
54 | runAndAwait(new RunInstancesRequest(imageId, min, max).withKeyName(keyPair.name).withInstanceType(instanceType))
55 | }
56 |
57 | @tailrec
58 | final def awaitInstances(awaiting: Seq[Instance], checkInterval: Long = 5000L): Seq[Instance] = {
59 | val requested = instances(awaiting.map(_.instanceId))
60 | if (requested.exists(_.state.getName == aws.model.InstanceStateName.Pending.toString)) {
61 | Thread.sleep(checkInterval)
62 | awaitInstances(awaiting, checkInterval)
63 | } else {
64 | requested
65 | }
66 | }
67 |
68 | def runAndAwait(request: aws.model.RunInstancesRequest): Seq[Instance] =
69 | awaitInstances(runInstances(request).getReservation.getInstances.asScala.map(Instance(_)).toSeq)
70 |
71 | def start(instance: Instance*) = startInstances(new aws.model.StartInstancesRequest()
72 | .withInstanceIds(instance.map(_.instanceId): _*))
73 |
74 | def stop(instance: Instance*) = stopInstances(new aws.model.StopInstancesRequest()
75 | .withInstanceIds(instance.map(_.instanceId): _*))
76 |
77 | def terminate(instance: Instance*) = terminateInstances(new aws.model.TerminateInstancesRequest()
78 | .withInstanceIds(instance.map(_.instanceId): _*))
79 |
80 | def reboot(instance: Instance*) = rebootInstances(new aws.model.RebootInstancesRequest()
81 | .withInstanceIds(instance.map(_.instanceId): _*))
82 |
83 | // ------------------------------------------
84 | // Key Pairs
85 | // ------------------------------------------
86 |
87 | def keyPairs: Seq[KeyPair] = describeKeyPairs.getKeyPairs.asScala.map(KeyPair(_)).toSeq
88 |
89 | def keyPair(name: String): Option[KeyPair] = {
90 | describeKeyPairs(new aws.model.DescribeKeyPairsRequest().withKeyNames(name))
91 | .getKeyPairs.asScala.map(KeyPair(_)).headOption
92 | }
93 |
94 | def createKeyPair(name: String): KeyPair = KeyPair(createKeyPair(new aws.model.CreateKeyPairRequest(name)).getKeyPair)
95 |
96 | def delete(keyPair: KeyPair): Unit = deleteKeyPair(keyPair.name)
97 | def deleteKeyPair(name: String): Unit = deleteKeyPair(new aws.model.DeleteKeyPairRequest(name))
98 |
99 | // ------------------------------------------
100 | // Security Groups
101 | // ------------------------------------------
102 |
103 | def securityGroups: Seq[SecurityGroup] = describeSecurityGroups.getSecurityGroups.asScala.map(SecurityGroup(_)).toSeq
104 |
105 | def securityGroup(name: String): Option[SecurityGroup] = {
106 | describeSecurityGroups(new aws.model.DescribeSecurityGroupsRequest().withGroupNames(name))
107 | .getSecurityGroups.asScala.map(SecurityGroup(_)).headOption
108 | }
109 |
110 | def createSecurityGroup(name: String, description: String): Option[SecurityGroup] = {
111 | createSecurityGroup(new aws.model.CreateSecurityGroupRequest(name, description))
112 | securityGroup(name)
113 | }
114 |
115 | def delete(securityGroup: SecurityGroup): Unit = deleteSecurityGroup(securityGroup.groupName)
116 | def deleteSecurityGroup(name: String): Unit = {
117 | deleteSecurityGroup(new aws.model.DeleteSecurityGroupRequest().withGroupName(name))
118 | }
119 |
120 | def tags(filters: Seq[aws.model.Filter] = Nil): Seq[aws.model.TagDescription] = {
121 | import aws.model.DescribeTagsResult
122 | object tagsSequencer extends Sequencer[aws.model.TagDescription, DescribeTagsResult, String] {
123 |
124 | val baseRequest = new aws.model.DescribeTagsRequest().withFilters(filters.asJava)
125 | def getInitial = describeTags(baseRequest)
126 | def getMarker(r: DescribeTagsResult) = r.getNextToken()
127 | def getFromMarker(marker: String) = describeTags(baseRequest.withNextToken(marker))
128 | def getList(r: DescribeTagsResult) = r.getTags()
129 | }
130 | tagsSequencer.sequence
131 | }
132 |
133 | def instanceStatuses(includeAll: Boolean = false, instanceIds: Seq[String] = Nil, filters: Seq[aws.model.Filter] = Nil): Seq[aws.model.InstanceStatus] = {
134 | import aws.model.DescribeInstanceStatusResult
135 |
136 | object instanceStatusSequencer extends Sequencer[aws.model.InstanceStatus, DescribeInstanceStatusResult, String] {
137 | val baseRequest = new aws.model.DescribeInstanceStatusRequest().withIncludeAllInstances(includeAll).withInstanceIds(instanceIds.asJava).withFilters(filters.asJava)
138 | def getInitial = describeInstanceStatus(baseRequest)
139 | def getMarker(r: DescribeInstanceStatusResult) = r.getNextToken()
140 | def getFromMarker(marker: String) = describeInstanceStatus(baseRequest.withNextToken(marker))
141 | def getList(r: DescribeInstanceStatusResult) = r.getInstanceStatuses()
142 | }
143 | instanceStatusSequencer.sequence
144 | }
145 |
146 | def reservedInstanceOfferings(availabilityZone: Option[String] = None, filters: Seq[aws.model.Filter] = Nil): Seq[aws.model.ReservedInstancesOffering] = {
147 | import aws.model.DescribeReservedInstancesOfferingsResult
148 |
149 | object reservedSequencer extends Sequencer[aws.model.ReservedInstancesOffering, DescribeReservedInstancesOfferingsResult, String] {
150 | val baseRequest = new aws.model.DescribeReservedInstancesOfferingsRequest().withFilters(filters.asJava)
151 | val base = if (availabilityZone == None) baseRequest else baseRequest.withAvailabilityZone(availabilityZone.get)
152 | def getInitial = describeReservedInstancesOfferings(base)
153 | def getMarker(r: DescribeReservedInstancesOfferingsResult) = r.getNextToken()
154 | def getFromMarker(marker: String) = describeReservedInstancesOfferings(base.withNextToken(marker))
155 | def getList(r: DescribeReservedInstancesOfferingsResult) = r.getReservedInstancesOfferings()
156 | }
157 | reservedSequencer.sequence
158 | }
159 | }
160 |
161 | /**
162 | * Default Implementation
163 | *
164 | * @param credentialsProvider credentialsProvider
165 | */
166 | class EC2Client(credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load())
167 | extends aws.AmazonEC2AsyncClient(credentialsProvider)
168 | with EC2
169 |
--------------------------------------------------------------------------------
/ec2/src/main/scala/awscala/ec2/Instance.scala:
--------------------------------------------------------------------------------
1 | package awscala.ec2
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.services.{ ec2 => aws }
6 | import java.util.Date
7 |
8 | object Instance {
9 |
10 | def apply(underlying: aws.model.Instance) = new Instance(underlying)
11 | }
12 |
13 | class Instance(val underlying: aws.model.Instance) {
14 |
15 | def start()(implicit ec2: EC2) = ec2.start(this)
16 | def stop()(implicit ec2: EC2) = ec2.stop(this)
17 | def terminate()(implicit ec2: EC2) = ec2.terminate(this)
18 | def reboot()(implicit ec2: EC2) = ec2.reboot(this)
19 |
20 | def withKeyPair[T](keyPairFile: File, user: String = "ec2-user", connectionTimeout: Int = 30000)(f: InstanceWithKeyPair => T): T = {
21 | f(InstanceWithKeyPair(underlying, keyPairFile, user, connectionTimeout))
22 | }
23 |
24 | def createImage(imageName: String)(implicit ec2: EC2) = {
25 | ec2.createImage(new aws.model.CreateImageRequest(instanceId, imageName))
26 | }
27 |
28 | def getName: Option[String] = tags.get("Name")
29 | def name: String = tags("Name")
30 |
31 | def instanceId: String = underlying.getInstanceId
32 |
33 | def instanceType: String = underlying.getInstanceType
34 |
35 | def imageId: String = underlying.getImageId
36 |
37 | def keyName: String = underlying.getKeyName
38 |
39 | def publicDnsName: String = underlying.getPublicDnsName
40 |
41 | def publicIpAddress: String = underlying.getPublicIpAddress
42 |
43 | def privateDnsName: String = underlying.getPrivateDnsName
44 |
45 | def privateIpAddress: String = underlying.getPrivateIpAddress
46 |
47 | def tags: Map[String, String] = underlying.getTags.asScala.map(t => t.getKey -> t.getValue).toMap
48 |
49 | def amiLaunchIndex: Int = underlying.getAmiLaunchIndex
50 |
51 | def architecture: String = underlying.getArchitecture
52 |
53 | def blockDeviceMappings: Seq[aws.model.InstanceBlockDeviceMapping] = underlying.getBlockDeviceMappings.asScala.toSeq
54 |
55 | def clientToken: String = underlying.getClientToken
56 |
57 | def ebsOptimized: Boolean = underlying.getEbsOptimized
58 |
59 | def hypervisor: Option[String] = Option(underlying.getHypervisor)
60 |
61 | def iamInstanceProfile: Option[aws.model.IamInstanceProfile] = Option(underlying.getIamInstanceProfile)
62 |
63 | def getInstanceLifecycle: Option[String] = Option(instanceLifecycle)
64 |
65 | def instanceLifecycle: String = underlying.getInstanceLifecycle
66 |
67 | def kernelId: String = underlying.getKernelId
68 |
69 | def launchTime: Date = underlying.getLaunchTime
70 |
71 | def monitoring: aws.model.Monitoring = underlying.getMonitoring
72 |
73 | def networkInterfaces: Seq[aws.model.InstanceNetworkInterface] = underlying.getNetworkInterfaces.asScala.toSeq
74 |
75 | def placement: aws.model.Placement = underlying.getPlacement
76 |
77 | def platform: Option[String] = Option(underlying.getPlatform)
78 |
79 | def productCodes: Seq[aws.model.ProductCode] = underlying.getProductCodes.asScala.toSeq
80 |
81 | def getRamdiskId: Option[String] = Option(ramdiskId)
82 |
83 | def ramdiskId: String = underlying.getRamdiskId
84 |
85 | def rootDeviceName: String = underlying.getRootDeviceName
86 |
87 | def rootDeviceType: String = underlying.getRootDeviceType
88 |
89 | def securityGroups: Seq[aws.model.GroupIdentifier] = underlying.getSecurityGroups.asScala.toSeq
90 |
91 | def spotInstanceRequestId: Option[String] = Option(underlying.getSpotInstanceRequestId)
92 |
93 | def state: aws.model.InstanceState = underlying.getState
94 |
95 | def stateReason: Option[aws.model.StateReason] = Option(underlying.getStateReason)
96 |
97 | //this sometimes returns empty string "" but seems not to return null.
98 | def stateTransitionReason: String = underlying.getStateTransitionReason
99 |
100 | def subnetId: Option[String] = Option(underlying.getSubnetId)
101 |
102 | def sourceDestCheck: Boolean = underlying.getSourceDestCheck
103 |
104 | def virtualizationType: Option[String] = Option(underlying.getVirtualizationType)
105 |
106 | def vpcId: Option[String] = Option(underlying.getVpcId)
107 |
108 | override def toString: String = s"Instance(${underlying.toString})"
109 |
110 | }
111 |
--------------------------------------------------------------------------------
/ec2/src/main/scala/awscala/ec2/InstanceType0.scala:
--------------------------------------------------------------------------------
1 | package awscala.ec2
2 |
3 | import com.amazonaws.services.{ ec2 => aws }
4 |
5 | object InstanceType0 {
6 | val T2_Small = aws.model.InstanceType.T2Small
7 | val T2_Micro = aws.model.InstanceType.T2Micro
8 | val T2_Medium = aws.model.InstanceType.T2Medium
9 | val C1_Medium = aws.model.InstanceType.C1Medium
10 | val C1_Xlarge = aws.model.InstanceType.C1Xlarge
11 | val Cc1_4xlarge = aws.model.InstanceType.Cc14xlarge
12 | val Cc2_8xlarge = aws.model.InstanceType.Cc28xlarge
13 | val Cg1_4xlarge = aws.model.InstanceType.Cg14xlarge
14 | val Cr1_8xlarge = aws.model.InstanceType.Cr18xlarge
15 | val Hi1_4xlarge = aws.model.InstanceType.Hi14xlarge
16 | val Hs1_8xlarge = aws.model.InstanceType.Hs18xlarge
17 | val M1_Large = aws.model.InstanceType.M1Large
18 | val M1_Medium = aws.model.InstanceType.M1Medium
19 | val M1_Small = aws.model.InstanceType.M1Small
20 | val M1_Xlarge = aws.model.InstanceType.M1Xlarge
21 | val M2_2xlarge = aws.model.InstanceType.M22xlarge
22 | val M2_4xlarge = aws.model.InstanceType.M24xlarge
23 | val M2_Xlarge = aws.model.InstanceType.M2Xlarge
24 | val M3_2xlarge = aws.model.InstanceType.M32xlarge
25 | val M3_Xlarge = aws.model.InstanceType.M3Xlarge
26 | val M3_Medium = aws.model.InstanceType.M3Medium
27 | val M3_Large = aws.model.InstanceType.M3Large
28 | val T1_Micro = aws.model.InstanceType.T1Micro
29 | val C3_2xlarge = aws.model.InstanceType.C32xlarge
30 | val C3_4xlarge = aws.model.InstanceType.C34xlarge
31 | val C3_8xlarge = aws.model.InstanceType.C38xlarge
32 | val C3_Large = aws.model.InstanceType.C3Large
33 | val C3_Xlarge = aws.model.InstanceType.C3Xlarge
34 | val C4_Large = aws.model.InstanceType.C4Large
35 | val C4_Xlarge = aws.model.InstanceType.C4Xlarge
36 | val C4_2xlarge = aws.model.InstanceType.C42xlarge
37 | val C4_4xlarge = aws.model.InstanceType.C44xlarge
38 | val C4_8xlarge = aws.model.InstanceType.C48xlarge
39 | val G2_2xlarge = aws.model.InstanceType.G22xlarge
40 | val I2_2xlarge = aws.model.InstanceType.I22xlarge
41 | val I2_4xlarge = aws.model.InstanceType.I24xlarge
42 | val I2_8xlarge = aws.model.InstanceType.I28xlarge
43 | val I2_Xlarge = aws.model.InstanceType.I2Xlarge
44 | val R3_2xlarge = aws.model.InstanceType.R32xlarge
45 | val R3_4xlarge = aws.model.InstanceType.R34xlarge
46 | val R3_8xlarge = aws.model.InstanceType.R38xlarge
47 | val R3_Large = aws.model.InstanceType.R3Large
48 | val R3_Xlarge = aws.model.InstanceType.R3Xlarge
49 | }
50 |
--------------------------------------------------------------------------------
/ec2/src/main/scala/awscala/ec2/InstanceWithKeyPair.scala:
--------------------------------------------------------------------------------
1 | package awscala.ec2
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ ec2 => aws }
5 | import com.decodified.scalassh.HostKeyVerifiers.DontVerify
6 |
7 | import scala.util.{ Failure, Success, Try }
8 |
9 | case class InstanceWithKeyPair(
10 | override val underlying: aws.model.Instance,
11 | keyPairFile: File,
12 | user: String,
13 | connectionTimeout: Int) extends Instance(underlying) {
14 |
15 | import com.decodified.scalassh._
16 |
17 | def ssh[T](f: SshClient => SSH.Result[T]): Try[T] = SSH[T](publicDnsName, provider(keyPairFile))(f)
18 |
19 | private[this] def provider(keyPairFile: File): HostConfigProvider = new HostConfigProvider {
20 | override def apply(v1: String): Try[HostConfig] =
21 | Success(HostConfig(
22 | login = PublicKeyLogin(user, None, List(keyPairFile.getAbsolutePath)),
23 | hostName = "",
24 | port = 22,
25 | connectTimeout = None,
26 | connectionTimeout = None,
27 | commandTimeout = Some(connectionTimeout),
28 | enableCompression = false,
29 | hostKeyVerifier = DontVerify,
30 | ptyConfig = None,
31 | sshjConfig = HostConfig.DefaultSshjConfig))
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/ec2/src/main/scala/awscala/ec2/Requests.scala:
--------------------------------------------------------------------------------
1 | package awscala.ec2
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.services.{ ec2 => aws }
5 |
6 | case class RunInstancesRequest(imageId: String, min: Int = 1, max: Int = 1)
7 | extends aws.model.RunInstancesRequest(imageId, min, max) {
8 |
9 | // TODO set...
10 | }
11 |
12 | object KeyPair {
13 | def apply(k: aws.model.KeyPair): KeyPair = KeyPair(k.getKeyName, k.getKeyFingerprint, Option(k.getKeyMaterial))
14 | def apply(k: aws.model.KeyPairInfo): KeyPair = KeyPair(k.getKeyName, k.getKeyFingerprint, None)
15 | }
16 | case class KeyPair(name: String, fingerprint: String, material: Option[String]) extends aws.model.KeyPair {
17 | override def getKeyName = name
18 | override def getKeyFingerprint = fingerprint
19 | override def getKeyMaterial = material.orNull
20 | }
21 |
22 | case class SecurityGroup(
23 | groupId: String,
24 | groupName: String,
25 | description: String,
26 | ipPermissions: Seq[IpPermission],
27 | ipPermissionsEgress: Seq[IpPermission],
28 | ownerId: String, tags: Map[String, String],
29 | vpcId: String) extends aws.model.SecurityGroup
30 |
31 | object SecurityGroup {
32 | def apply(k: aws.model.SecurityGroup): SecurityGroup =
33 | SecurityGroup(
34 | k.getGroupId,
35 | k.getGroupName,
36 | k.getDescription,
37 | k.getIpPermissions.asScala.toSeq.map(IpPermission(_)),
38 | k.getIpPermissionsEgress.asScala.toSeq.map(IpPermission(_)),
39 | k.getOwnerId,
40 | k.getTags.asScala.map(t => t.getKey -> t.getValue).toMap,
41 | k.getVpcId)
42 | }
43 |
44 | object IpPermission {
45 | def apply(i: aws.model.IpPermission): IpPermission = {
46 | IpPermission(
47 | fromPort = if (i.getFromPort == null) -1 else i.getFromPort,
48 | toPort = if (i.getToPort == null) -1 else i.getToPort,
49 | ipRanges = i.getIpv4Ranges.asScala.toSeq.map(_.getCidrIp),
50 | ipProtocol = i.getIpProtocol,
51 | userIdGroupPairs = i.getUserIdGroupPairs.asScala.toSeq.map(UserIdGroupPair(_)))
52 |
53 | }
54 | }
55 | case class IpPermission(
56 | fromPort: Int,
57 | toPort: Int,
58 | ipRanges: Seq[String],
59 | ipProtocol: String,
60 | userIdGroupPairs: Seq[UserIdGroupPair]) extends aws.model.IpPermission
61 |
62 | object UserIdGroupPair {
63 | def apply(u: aws.model.UserIdGroupPair): UserIdGroupPair = {
64 | UserIdGroupPair(
65 | groupId = u.getGroupId,
66 | groupName = u.getGroupName,
67 | userId = u.getUserId)
68 | }
69 | }
70 | case class UserIdGroupPair(groupId: String, groupName: String, userId: String)
71 | extends aws.model.UserIdGroupPair
72 |
73 |
--------------------------------------------------------------------------------
/ec2/src/main/scala/awscala/ec2/SSHEnabledInstance.scala:
--------------------------------------------------------------------------------
1 | package awscala.ec2
2 |
3 | import com.decodified.scalassh._
4 | import java.io.File
5 |
6 | case class SSHEnabledInstance(instance: Instance) {
7 |
8 | def ssh[T](f: SshClient => SSH.Result[T], keyPairFilePath: String): SSH.Result[T] = ssh(f, new File(keyPairFilePath))
9 |
10 | def ssh[T](f: SshClient => SSH.Result[T], keyPairFile: File): SSH.Result[T] = {
11 | instance.withKeyPair(keyPairFile)(_.ssh(f))
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/ec2/src/main/scala/awscala/ec2/package.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import scala.language.implicitConversions
4 |
5 | package object ec2 {
6 | // Workaround for https://issues.scala-lang.org/browse/SI-7139
7 | val InstanceType = InstanceType0
8 | type InstanceType = com.amazonaws.services.ec2.model.InstanceType
9 |
10 | implicit def fromInstanceToSSHEnabledInstance(instance: Instance): SSHEnabledInstance = SSHEnabledInstance(instance)
11 |
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/ec2/src/test/scala/awscala/EC2Spec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import awscala._
4 | import ec2._
5 | import org.slf4j._
6 | import org.scalatest._
7 | import java.io._
8 | import org.scalatest.flatspec.AnyFlatSpec
9 | import org.scalatest.matchers.should.Matchers
10 |
11 | class EC2Spec extends AnyFlatSpec with Matchers {
12 |
13 | behavior of "EC2"
14 |
15 | val log = LoggerFactory.getLogger(this.getClass)
16 |
17 | it should "provide cool APIs" in {
18 | implicit val ec2 = EC2.at(Region.Tokyo)
19 |
20 | val groupName = s"awscala-unit-test-securitygroup-${System.currentTimeMillis}"
21 | val groupDescription = "for awscala unit test"
22 | val keyPairName = s"awscala-unit-test-keypair-${System.currentTimeMillis}"
23 |
24 | //create SecurityGroup
25 | ec2.createSecurityGroup(groupName, groupDescription).foreach { s =>
26 | log.info(s"Created SecurityGroup:${s}")
27 | }
28 |
29 | //create keyPair
30 | val kpFile: java.io.File = new java.io.File(keyPairName + ".pem")
31 | val keyPair = ec2.createKeyPair(keyPairName)
32 |
33 | keyPair.material.foreach { m =>
34 | log.info(s"Created KeyPair:${m}")
35 | val pw = new PrintWriter(kpFile)
36 | pw.println(m)
37 | pw.close()
38 | sys.process.Process(s"chmod 600 ${kpFile.getAbsolutePath}")
39 | }
40 |
41 | ec2.runAndAwait("ami-2819aa29", keyPair).headOption.foreach { instance =>
42 | instance.withKeyPair(kpFile) { i =>
43 | i.ssh { client =>
44 | client.exec("ls -la").map { result =>
45 | log.info(s"Run command on this EC2 instance:${instance.instanceId} Result:\n" + result.stdOutAsString())
46 | }
47 | }
48 | }
49 | kpFile.delete()
50 |
51 | instance.terminate()
52 |
53 | //Unless EC2 instance has been terminated, you cannot delete SecurityGroup.
54 | while (!ec2.instances.exists(i => i.instanceId == instance.instanceId && i.state.getName == "terminated")) {
55 | Thread.sleep(3000L)
56 | }
57 | ec2.deleteKeyPair(keyPairName)
58 | ec2.deleteSecurityGroup(groupName)
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/ec2/src/test/scala/awscala/KeyPairSpec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import awscala._, ec2._
4 |
5 | import org.slf4j._
6 | import org.scalatest._
7 |
8 | import java.io._
9 | import org.scalatest.flatspec.AnyFlatSpec
10 | import org.scalatest.matchers.should.Matchers
11 |
12 | class KeyPairSpec extends AnyFlatSpec with Matchers {
13 |
14 | behavior of "KeyPair"
15 |
16 | val log = LoggerFactory.getLogger(this.getClass)
17 |
18 | it should "properly implement the inherited Java methods" in {
19 | implicit val ec2 = EC2.at(Region.Tokyo)
20 |
21 | val keyPairName = s"awscala-unit-test-keypair-${System.currentTimeMillis}-${new scala.util.Random().nextInt(100)}"
22 | val keyPair = ec2.createKeyPair(keyPairName)
23 |
24 | try {
25 | keyPair.name should be(keyPairName)
26 | keyPair.fingerprint.size should be > (0)
27 | // Newly created key pair has material (i.e. private key)
28 | keyPair.material should be(Symbol("defined"))
29 | keyPair.material.get.size should be > (0)
30 |
31 | keyPair.getKeyName should equal(keyPair.name)
32 | keyPair.getKeyFingerprint should equal(keyPair.fingerprint)
33 | keyPair.getKeyMaterial should equal(keyPair.material.get)
34 |
35 | // Key pair without private key info defined
36 | KeyPair("my-key", "fingerprint", None).getKeyMaterial should be(null)
37 |
38 | } finally {
39 | ec2.deleteKeyPair(keyPairName)
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/emr/src/main/scala/awscala/emr/Cluster.scala:
--------------------------------------------------------------------------------
1 | package awscala.emr
2 |
3 | import com.amazonaws.services.{ elasticmapreduce => aws }
4 | import scala.jdk.CollectionConverters._
5 |
6 | case class Cluster(in: aws.model.Cluster) extends aws.model.Cluster {
7 | setApplications(in.getApplications())
8 | setAutoTerminate(in.getAutoTerminate())
9 | setEc2InstanceAttributes(in.getEc2InstanceAttributes())
10 | setId(in.getId())
11 | setLogUri(in.getLogUri())
12 | setName(in.getName())
13 | setRequestedAmiVersion(in.getRequestedAmiVersion())
14 | setRunningAmiVersion(in.getRunningAmiVersion())
15 | setStatus(in.getStatus)
16 | setTags(in.getTags())
17 | setTerminationProtected(in.getTerminationProtected())
18 | setVisibleToAllUsers(in.getVisibleToAllUsers())
19 |
20 | private var cachedBootstrapActions: Option[Seq[aws.model.Command]] = None
21 | private var cachedStepSummaries: Option[Seq[aws.model.StepSummary]] = None
22 |
23 | def name = getName()
24 | def id = getId()
25 | lazy val applications = getApplications().asScala
26 | def autoTerminate = getAutoTerminate()
27 | def logUri = getLogUri()
28 | def requestedAmiVersion = getRequestedAmiVersion()
29 | def runningAmiVersion = getRunningAmiVersion()
30 | lazy val tags = getTags().asScala
31 | def terminationProtected = getTerminationProtected()
32 | def visibleToAllUsers = getVisibleToAllUsers()
33 |
34 | def bootstrapActions(implicit emr: EMR): Seq[aws.model.Command] = {
35 | cachedBootstrapActions match {
36 | case None =>
37 | cachedBootstrapActions = Some(emr.bootstrapActions(Some(getId)))
38 | cachedBootstrapActions.get
39 | case Some(e) => e
40 | }
41 | }
42 |
43 | def stepSummaries(implicit emr: EMR): Seq[aws.model.StepSummary] = {
44 | cachedStepSummaries match {
45 | case None =>
46 | cachedStepSummaries = Some(emr.stepSummaries(Some(getId)))
47 | cachedStepSummaries.get
48 | case Some(e) => e
49 | }
50 | }
51 |
52 | def status(implicit emr: EMR): aws.model.ClusterStatus = {
53 | val describeClusterRequest = new aws.model.DescribeClusterRequest().withClusterId(getId)
54 | emr.describeCluster(describeClusterRequest).getCluster().getStatus()
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/AccessKey.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import com.amazonaws.services.{ identitymanagement => aws }
4 | import java.util.Date
5 |
6 | object AccessKey {
7 |
8 | def apply(ak: aws.model.AccessKey): AccessKey = new AccessKey(
9 | userName = ak.getUserName,
10 | accessKeyId = ak.getAccessKeyId,
11 | secretAccessKey = Option(ak.getSecretAccessKey),
12 | status = ak.getStatus,
13 | createDate = ak.getCreateDate)
14 | def apply(m: aws.model.AccessKeyMetadata): AccessKey = new AccessKey(
15 | userName = m.getUserName,
16 | accessKeyId = m.getAccessKeyId,
17 | secretAccessKey = None,
18 | status = m.getStatus,
19 | createDate = m.getCreateDate)
20 | }
21 |
22 | case class AccessKey(userName: String, accessKeyId: String, secretAccessKey: Option[String], status: String, createDate: Date)
23 | extends aws.model.AccessKey(userName, accessKeyId, status, secretAccessKey.orNull[String]) {
24 |
25 | def activate()(implicit iam: IAM) = iam.activateAccessKey(this)
26 | def inactivate()(implicit iam: IAM) = iam.inactivateAccessKey(this)
27 | def destroy()(implicit iam: IAM) = iam.delete(this)
28 | }
29 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/Group.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ identitymanagement => aws }
5 |
6 | object Group {
7 |
8 | def apply(g: aws.model.Group): Group = new Group(
9 | id = g.getGroupId,
10 | name = g.getGroupName,
11 | arn = g.getArn,
12 | path = g.getPath,
13 | createdAt = new DateTime(g.getCreateDate))
14 | }
15 |
16 | case class Group(id: String, name: String, arn: String, path: String, createdAt: DateTime)
17 | extends aws.model.Group(path, name, id, arn, createdAt.toDate) {
18 |
19 | def updateName(newName: String)(implicit iam: IAM) = iam.updateGroupName(this, newName)
20 | def updatePath(newPath: String)(implicit iam: IAM) = iam.updateGroupPath(this, newPath)
21 |
22 | // users
23 | def add(user: User)(implicit iam: IAM) = addUser(user)
24 | def addUser(user: User)(implicit iam: IAM) = iam.addUserToGroup(this, user)
25 | def remove(user: User)(implicit iam: IAM) = removeUser(user)
26 | def removeUser(user: User)(implicit iam: IAM) = iam.removeUserFromGroup(this, user)
27 |
28 | // policies
29 | def policyNames()(implicit iam: IAM) = iam.groupPolicyNames(this)
30 | def policy(name: String)(implicit iam: IAM) = iam.groupPolicy(this, name)
31 | def putPolicy(name: String, policy: Policy)(implicit iam: IAM) = iam.putGroupPolicy(this, name, policy.toJSON)
32 | def putPolicy(name: String, document: String)(implicit iam: IAM) = iam.putGroupPolicy(this, name, document)
33 | def remove(policy: GroupPolicy)(implicit iam: IAM) = removePolicy(policy)
34 | def removePolicy(policy: GroupPolicy)(implicit iam: IAM) = iam.deleteGroupPolicy(policy)
35 |
36 | def destroy()(implicit iam: IAM) = iam.delete(this)
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/GroupPolicy.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import com.amazonaws.services.{ identitymanagement => aws }
4 | import awscala.Policy
5 |
6 | object GroupPolicy {
7 | def apply(group: Group, r: aws.model.GetGroupPolicyResult): GroupPolicy = GroupPolicy(
8 | group = group,
9 | name = r.getPolicyName,
10 | document = r.getPolicyDocument)
11 | }
12 |
13 | case class GroupPolicy(group: Group, name: String, document: String) {
14 |
15 | def this(group: Group, name: String, document: Policy) = {
16 | this(group, name, document.asJSON)
17 | }
18 |
19 | def destroy()(implicit iam: IAM): Unit = iam.delete(this)
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/InstanceProfile.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.services.{ identitymanagement => aws }
6 |
7 | object InstanceProfile {
8 |
9 | def apply(g: aws.model.InstanceProfile): InstanceProfile = new InstanceProfile(
10 | id = g.getInstanceProfileId,
11 | name = g.getInstanceProfileName,
12 | arn = g.getArn,
13 | path = g.getPath,
14 | roles = g.getRoles.asScala.map(r => Role(r)).toSeq,
15 | createdAt = new DateTime(g.getCreateDate))
16 | }
17 |
18 | case class InstanceProfile(id: String, name: String, arn: String, path: String, roles: Seq[Role], createdAt: DateTime)
19 | extends aws.model.InstanceProfile {
20 |
21 | setArn(arn)
22 | setCreateDate(createdAt.toDate)
23 | setInstanceProfileId(id)
24 | setInstanceProfileName(name)
25 | setPath(path)
26 | setRoles(roles.map(_.asInstanceOf[aws.model.Role]).asJava)
27 |
28 | def add(role: Role)(implicit iam: IAM) = addRole(role)
29 | def addRole(role: Role)(implicit iam: IAM) = iam.addRoleToInstanceProfile(this, role)
30 |
31 | def remove(role: Role)(implicit iam: IAM) = removeRole(role)
32 | def removeRole(role: Role)(implicit iam: IAM) = iam.removeRoleFromInstanceProfile(this, role)
33 |
34 | def destroy()(implicit iam: IAM) = iam.delete(this)
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/LoginProfile.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.services.{ identitymanagement => aws }
6 |
7 | object LoginProfile {
8 |
9 | def apply(user: User, g: aws.model.LoginProfile): LoginProfile = new LoginProfile(
10 | user = user,
11 | createdAt = new DateTime(g.getCreateDate))
12 | }
13 |
14 | case class LoginProfile(user: User, createdAt: DateTime)
15 | extends aws.model.LoginProfile {
16 |
17 | setUserName(user.name)
18 | setCreateDate(createdAt.toDate)
19 |
20 | def changePassword(newPassword: String)(implicit iam: IAM) = iam.changeUserPassword(this, newPassword)
21 | def destroy()(implicit iam: IAM) = iam.delete(this)
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/Role.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ identitymanagement => aws }
5 |
6 | object Role {
7 |
8 | def apply(g: aws.model.Role): Role = new Role(
9 | id = g.getRoleId,
10 | name = g.getRoleName,
11 | arn = g.getArn,
12 | path = g.getPath,
13 | assumeRolePolicyDocument = g.getAssumeRolePolicyDocument,
14 | createdAt = new DateTime(g.getCreateDate))
15 | }
16 |
17 | case class Role(id: String, name: String, arn: String, path: String, assumeRolePolicyDocument: String, createdAt: DateTime)
18 | extends aws.model.Role {
19 |
20 | setArn(arn)
21 | setAssumeRolePolicyDocument(assumeRolePolicyDocument)
22 | setCreateDate(createdAt.toDate)
23 | setPath(path)
24 | setRoleId(id)
25 | setRoleName(name)
26 |
27 | // instance profiles
28 | def instanceProfiles()(implicit iam: IAM): Seq[InstanceProfile] = iam.instanceProfiles(this)
29 |
30 | // policies
31 | def policyNames()(implicit iam: IAM) = iam.rolePolicyNames(this)
32 | def policy(name: String)(implicit iam: IAM) = iam.rolePolicy(this, name)
33 | def putPolicy(name: String, policy: Policy)(implicit iam: IAM) = iam.putRolePolicy(this, name, policy.toJSON)
34 | def putPolicy(name: String, document: String)(implicit iam: IAM) = iam.putRolePolicy(this, name, document)
35 | def remove(policy: RolePolicy)(implicit iam: IAM) = removePolicy(policy)
36 | def removePolicy(policy: RolePolicy)(implicit iam: IAM) = iam.deleteRolePolicy(policy)
37 |
38 | def destroy()(implicit iam: IAM) = iam.delete(this)
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/RolePolicy.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import com.amazonaws.services.{ identitymanagement => aws }
4 |
5 | object RolePolicy {
6 | def apply(role: Role, r: aws.model.GetRolePolicyResult): RolePolicy = RolePolicy(
7 | role = role,
8 | name = r.getPolicyName,
9 | document = r.getPolicyDocument)
10 | }
11 |
12 | case class RolePolicy(role: Role, name: String, document: String) {
13 |
14 | def destroy()(implicit iam: IAM) = iam.delete(this)
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/User.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ identitymanagement => aws }
5 |
6 | object User {
7 |
8 | def apply(g: aws.model.User): User = new User(
9 | id = g.getUserId,
10 | name = g.getUserName,
11 | arn = g.getArn,
12 | path = g.getPath,
13 | createdAt = new DateTime(g.getCreateDate))
14 | }
15 |
16 | case class User(id: String, name: String, arn: String, path: String, createdAt: DateTime)
17 | extends aws.model.User(path, name, id, arn, createdAt.toDate) {
18 |
19 | def updateName(name: String)(implicit iam: IAM) = iam.updateUserName(this, name)
20 | def updatePath(path: String)(implicit iam: IAM) = iam.updateUserPath(this, path)
21 |
22 | // login profile
23 | def setLoginPassword(password: String)(implicit iam: IAM) = iam.createLoginProfile(this, password)
24 | def loginProfile()(implicit iam: IAM): Option[LoginProfile] = iam.loginProfile(this)
25 |
26 | // groups
27 | def groups()(implicit iam: IAM): Seq[Group] = iam.groups(this)
28 | def join(group: Group)(implicit iam: IAM) = iam.addUserToGroup(group, this)
29 | def leave(group: Group)(implicit iam: IAM) = iam.removeUserFromGroup(group, this)
30 |
31 | // policies
32 | def policyNames()(implicit iam: IAM) = iam.policyNames(this)
33 | def policy(name: String)(implicit iam: IAM) = iam.userPolicy(this, name)
34 | def putPolicy(name: String, policy: Policy)(implicit iam: IAM) = iam.putUserPolicy(this, name, policy.toJSON)
35 | def putPolicy(name: String, document: String)(implicit iam: IAM) = iam.putUserPolicy(this, name, document)
36 | def remove(policy: UserPolicy)(implicit iam: IAM) = removePolicy(policy)
37 | def removePolicy(policy: UserPolicy)(implicit iam: IAM) = iam.deleteUserPolicy(policy)
38 |
39 | // access keys
40 | def accessKeys()(implicit iam: IAM): Seq[AccessKey] = iam.accessKeys(this)
41 | def createAccessKey()(implicit iam: IAM): AccessKey = iam.createAccessKey(this)
42 |
43 | // MFA devices
44 | def virtualMFADevices()(implicit iam: IAM): Seq[VirtualMFADevice] = iam.virtualMFADevices(this)
45 | def add(device: VirtualMFADevice, code1: String, code2: String)(implicit iam: IAM) = {
46 | addVirtualMFADevice(device, code1, code2)
47 | }
48 | def addVirtualMFADevice(device: VirtualMFADevice, code1: String, code2: String)(implicit iam: IAM) = {
49 | iam.enableVirtualMFADevice(device, this, code1, code2)
50 | }
51 | def remove(device: VirtualMFADevice)(implicit iam: IAM) = removeVirtualMFADevice(device)
52 | def removeVirtualMFADevice(device: VirtualMFADevice)(implicit iam: IAM) = {
53 | iam.disableVirtualMFADevice(device, this)
54 | }
55 |
56 | def destroy()(implicit iam: IAM) = iam.delete(this)
57 | }
58 |
59 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/UserPolicy.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import com.amazonaws.services.{ identitymanagement => aws }
4 |
5 | object UserPolicy {
6 | def apply(user: User, r: aws.model.GetUserPolicyResult): UserPolicy = UserPolicy(
7 | user = user,
8 | name = r.getPolicyName,
9 | document = r.getPolicyDocument)
10 | }
11 |
12 | case class UserPolicy(user: User, name: String, document: String) {
13 |
14 | def destroy()(implicit iam: IAM) = iam.delete(this)
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/VirtualMFADevice.scala:
--------------------------------------------------------------------------------
1 | package awscala.iam
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ identitymanagement => aws }
5 |
6 | object VirtualMFADevice {
7 |
8 | def apply(user: User, g: aws.model.MFADevice): VirtualMFADevice = new VirtualMFADevice(
9 | base32StringSeed = None,
10 | enabledAt = new DateTime(g.getEnableDate),
11 | qrCodePng = None,
12 | serialNumber = g.getSerialNumber,
13 | user = user)
14 | def apply(g: aws.model.VirtualMFADevice): VirtualMFADevice = new VirtualMFADevice(
15 | base32StringSeed = Some(g.getBase32StringSeed),
16 | enabledAt = new DateTime(g.getEnableDate),
17 | qrCodePng = Some(g.getQRCodePNG),
18 | serialNumber = g.getSerialNumber,
19 | user = User(g.getUser))
20 | }
21 |
22 | case class VirtualMFADevice(
23 | serialNumber: String, base32StringSeed: Option[java.nio.ByteBuffer], qrCodePng: Option[java.nio.ByteBuffer],
24 | user: User, enabledAt: DateTime)
25 | extends aws.model.VirtualMFADevice {
26 |
27 | setBase32StringSeed(base32StringSeed.orNull[java.nio.ByteBuffer])
28 | setEnableDate(enabledAt.toDate)
29 | setQRCodePNG(qrCodePng.orNull[java.nio.ByteBuffer])
30 | setSerialNumber(serialNumber)
31 | setUser(user)
32 |
33 | def enable(user: User, code1: String, code2: String)(implicit iam: IAM) = {
34 | iam.enableVirtualMFADevice(this, user, code1, code2)
35 | }
36 | def disable(user: User)(implicit iam: IAM) = {
37 | iam.disableVirtualMFADevice(this, user)
38 | }
39 |
40 | def destroy()(implicit iam: IAM) = iam.delete(this)
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/iam/src/main/scala/awscala/iam/package.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | package object iam {
4 |
5 | type StatusType = com.amazonaws.services.identitymanagement.model.StatusType
6 |
7 | }
8 |
9 |
--------------------------------------------------------------------------------
/iam/src/test/scala/awscala/IAMSpec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import java.util.Date
4 |
5 | import awscala.iam._
6 | import org.slf4j._
7 | import org.scalatest._
8 | import org.scalatest.flatspec.AnyFlatSpec
9 | import org.scalatest.matchers.should.Matchers
10 |
11 | class IAMSpec extends AnyFlatSpec with Matchers {
12 |
13 | behavior of "IAM"
14 |
15 | val log = LoggerFactory.getLogger(this.getClass)
16 |
17 | it should "provide cool APIs" in {
18 | implicit val iam = IAM()
19 |
20 | val groupName = s"awscala-unit-test-group-${System.currentTimeMillis}"
21 | val userName = s"awscala-unit-test-user-${System.currentTimeMillis}"
22 |
23 | val group: Group = iam.createGroup(groupName)
24 | log.info(s"Created Group: ${group}")
25 |
26 | val user: User = iam.createUser(userName)
27 | // user.setLoginPassword("dummy-password-xxx-yyy") // takes long time to prepare
28 | log.info(s"Created User: ${user}")
29 |
30 | group.add(user)
31 | group.remove(user)
32 |
33 | val policyName = s"awscala-unit-test-group-policy-${System.currentTimeMillis}"
34 |
35 | val policy: Policy = Policy(Seq(Statement(Effect.Allow, Seq(Action("s3:*")), Seq(Resource("*")))))
36 | group.putPolicy(policyName, policy)
37 |
38 | group.policyNames().foreach { policyName =>
39 | group.policy(policyName).destroy()
40 | }
41 | group.destroy()
42 |
43 | user.destroy()
44 | }
45 |
46 | it should "return correct dates for access keys" in {
47 | implicit val iam = IAM()
48 | val userName = s"awscala-unit-test-user-${System.currentTimeMillis}"
49 | val user: User = iam.createUser(userName)
50 | val userAccessKey = user.createAccessKey()
51 | val accessKeyCreatedDate = userAccessKey.createDate
52 | accessKeyCreatedDate.getTime should equal(new Date().getTime +- 1000)
53 | }
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.8.2
2 |
--------------------------------------------------------------------------------
/project/plugins.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.3")
2 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4")
3 | addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "3.9.18")
4 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
5 | addSbtPlugin("com.localytics" % "sbt-dynamodb" % "2.0.3")
6 |
7 | scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
8 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/AccountWithRestoreAccess.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | case class AccountWithRestoreAccess(accountId: String) extends aws.model.AccountWithRestoreAccess {
6 |
7 | setAccountId(accountId)
8 | }
9 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/Cluster.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.services.{ redshift => aws }
6 |
7 | object Cluster {
8 | def apply(c: aws.model.Cluster): Cluster = new Cluster(
9 | identifier = c.getClusterIdentifier,
10 | dbName = c.getDBName,
11 | endpoint = Endpoint(c.getEndpoint),
12 | masterUserName = c.getMasterUsername,
13 | status = c.getClusterStatus,
14 | version = ClusterVersion(c.getClusterVersion),
15 | nodeType = NodeType(c.getNodeType),
16 | numOfNodes = c.getNumberOfNodes,
17 | modifyStatus = c.getModifyStatus,
18 | availabilityZone = AvailabilityZone(c.getAvailabilityZone),
19 | encrypted = c.isEncrypted,
20 | allowVersionUpgrade = c.isAllowVersionUpgrade,
21 | publiclyAccessible = c.isPubliclyAccessible,
22 | automatedSnapshotRetentionPeriod = c.getAutomatedSnapshotRetentionPeriod,
23 | subnetGroupName = c.getClusterSubnetGroupName,
24 | restoreStatus = RestoreStatus(c.getRestoreStatus),
25 | preferredMaintenanceWindow = c.getPreferredMaintenanceWindow,
26 | pendingModifiedValues = PendingModifiedValues(c.getPendingModifiedValues),
27 | parameterGroupStatuses = c.getClusterParameterGroups.asScala.map(p => ClusterParameterGroupStatus(p)).toSeq,
28 | securityGroupMemberships = c.getClusterSecurityGroups.asScala.map(m => ClusterSecurityGroupMembership(m)).toSeq,
29 | vpcId = c.getVpcId,
30 | vpcSecurityGroupMemberships = c.getVpcSecurityGroups.asScala.map(m => VpcSecurityGroupMembership(m)).toSeq,
31 | createdAt = new DateTime(c.getClusterCreateTime))
32 | }
33 |
34 | class Cluster(
35 | val identifier: String,
36 | val dbName: String,
37 | val endpoint: Endpoint,
38 | val masterUserName: String,
39 | val status: String,
40 | val version: ClusterVersion,
41 | val nodeType: NodeType,
42 | val numOfNodes: Int,
43 | val modifyStatus: String,
44 | val availabilityZone: AvailabilityZone,
45 | val encrypted: Boolean,
46 | val allowVersionUpgrade: Boolean,
47 | val publiclyAccessible: Boolean,
48 | val automatedSnapshotRetentionPeriod: Int,
49 | val subnetGroupName: String,
50 | val restoreStatus: Option[RestoreStatus],
51 | val preferredMaintenanceWindow: String,
52 | val pendingModifiedValues: PendingModifiedValues,
53 | val parameterGroupStatuses: Seq[ClusterParameterGroupStatus],
54 | val securityGroupMemberships: Seq[ClusterSecurityGroupMembership],
55 | val vpcId: String,
56 | val vpcSecurityGroupMemberships: Seq[VpcSecurityGroupMembership],
57 | val createdAt: DateTime) extends aws.model.Cluster {
58 |
59 | setAllowVersionUpgrade(allowVersionUpgrade)
60 | setAutomatedSnapshotRetentionPeriod(automatedSnapshotRetentionPeriod)
61 | setAvailabilityZone(availabilityZone.name)
62 | setClusterCreateTime(createdAt.toDate)
63 | setClusterIdentifier(identifier)
64 | setClusterParameterGroups(parameterGroupStatuses.map(_.asInstanceOf[aws.model.ClusterParameterGroupStatus]).asJava)
65 | setClusterSecurityGroups(securityGroupMemberships.map(_.asInstanceOf[aws.model.ClusterSecurityGroupMembership]).asJava)
66 | setClusterStatus(status)
67 | setClusterSubnetGroupName(subnetGroupName)
68 | setClusterVersion(version.version)
69 | setDBName(dbName)
70 | setEncrypted(encrypted)
71 | setEndpoint(endpoint)
72 | setMasterUsername(masterUserName)
73 | setModifyStatus(modifyStatus)
74 | setNodeType(nodeType.value)
75 | setNumberOfNodes(numOfNodes)
76 | setPendingModifiedValues(pendingModifiedValues)
77 | setPreferredMaintenanceWindow(preferredMaintenanceWindow)
78 | setPubliclyAccessible(publiclyAccessible)
79 | setRestoreStatus(restoreStatus.orNull[aws.model.RestoreStatus])
80 | setVpcId(vpcId)
81 | setVpcSecurityGroups(vpcSecurityGroupMemberships.map(_.asInstanceOf[aws.model.VpcSecurityGroupMembership]).asJava)
82 |
83 | def jdbcUrl: String = s"jdbc:postgresql://${endpoint.address}:${endpoint.port}/${dbName}"
84 |
85 | def destroy(finalSnapshotIdentifier: String)(implicit redshift: Redshift) = redshift.delete(this, finalSnapshotIdentifier)
86 |
87 | def destroyWithoutFinalSnapshot()(implicit redshift: Redshift) = redshift.deleteWithoutFinalSnapshot(this)
88 |
89 | }
90 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/ClusterParameterGroup.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | object ClusterParameterGroup {
6 | def apply(g: aws.model.ClusterParameterGroup): ClusterParameterGroup = new ClusterParameterGroup(
7 | name = g.getParameterGroupName,
8 | family = g.getParameterGroupFamily,
9 | description = g.getDescription)
10 | }
11 | case class ClusterParameterGroup(name: String, family: String, description: String) extends aws.model.ClusterParameterGroup {
12 | setParameterGroupName(name)
13 | setParameterGroupFamily(family)
14 | setDescription(description)
15 |
16 | def destroy()(implicit redshift: Redshift) = redshift.delete(this)
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/ClusterParameterGroupStatus.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | object ClusterParameterGroupStatus {
6 | def apply(s: aws.model.ClusterParameterGroupStatus): ClusterParameterGroupStatus = new ClusterParameterGroupStatus(
7 | applyStatus = s.getParameterApplyStatus,
8 | groupName = s.getParameterGroupName)
9 | }
10 | case class ClusterParameterGroupStatus(applyStatus: String, groupName: String) extends aws.model.ClusterParameterGroupStatus {
11 | setParameterApplyStatus(applyStatus)
12 | setParameterGroupName(groupName)
13 | }
14 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/ClusterSecurityGroup.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.services.{ redshift => aws }
5 |
6 | object ClusterSecurityGroup {
7 | def apply(g: aws.model.ClusterSecurityGroup): ClusterSecurityGroup = new ClusterSecurityGroup(
8 | name = g.getClusterSecurityGroupName,
9 | ec2SecurityGroups = g.getEC2SecurityGroups.asScala.map(g =>
10 | EC2SecurityGroup(g.getEC2SecurityGroupName, g.getEC2SecurityGroupOwnerId, g.getStatus)).toSeq,
11 | ipranges = g.getIPRanges.asScala.map(i => IPRange(i.getCIDRIP, i.getStatus)).toSeq,
12 | description = g.getDescription)
13 | }
14 | case class ClusterSecurityGroup(
15 | name: String, ec2SecurityGroups: Seq[EC2SecurityGroup], ipranges: Seq[IPRange], description: String)
16 | extends aws.model.ClusterSecurityGroup {
17 |
18 | setClusterSecurityGroupName(name)
19 | setDescription(description)
20 | setEC2SecurityGroups(ec2SecurityGroups.map(_.asInstanceOf[aws.model.EC2SecurityGroup]).asJava)
21 | setIPRanges(ipranges.map(_.asInstanceOf[aws.model.IPRange]).asJava)
22 |
23 | def destroy()(implicit redshift: Redshift) = redshift.delete(this)
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/ClusterSecurityGroupMembership.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | object ClusterSecurityGroupMembership {
6 | def apply(m: aws.model.ClusterSecurityGroupMembership): ClusterSecurityGroupMembership = new ClusterSecurityGroupMembership(
7 | groupName = m.getClusterSecurityGroupName,
8 | status = m.getStatus)
9 | }
10 | case class ClusterSecurityGroupMembership(groupName: String, status: String) extends aws.model.ClusterSecurityGroupMembership {
11 | setClusterSecurityGroupName(groupName)
12 | setStatus(status)
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/ClusterSubnetGroup.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.services.{ redshift => aws }
5 |
6 | object ClusterSubnetGroup {
7 |
8 | def apply(g: aws.model.ClusterSubnetGroup): ClusterSubnetGroup = new ClusterSubnetGroup(
9 | name = g.getClusterSubnetGroupName,
10 | description = g.getDescription,
11 | status = g.getSubnetGroupStatus,
12 | subnets = g.getSubnets.asScala.map(s => Subnet(s)).toSeq,
13 | vpcId = g.getVpcId)
14 | }
15 |
16 | case class ClusterSubnetGroup(
17 | name: String, description: String, status: String, subnets: Seq[Subnet], vpcId: String)
18 | extends aws.model.ClusterSubnetGroup {
19 |
20 | setClusterSubnetGroupName(name)
21 | setDescription(description)
22 | setSubnetGroupStatus(status)
23 | setSubnets(subnets.map(_.asInstanceOf[aws.model.Subnet]).asJava)
24 | setVpcId(vpcId)
25 |
26 | def destroy()(implicit redshift: Redshift) = redshift.delete(this)
27 | }
28 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/ClusterType.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | object ClusterType {
4 | val SingleNode = ClusterType("single-node")
5 | val MultiNode = ClusterType("multi-node")
6 | }
7 | case class ClusterType(name: String)
8 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/ClusterVersion.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | object ClusterVersion {
6 | val Version_1_0 = ClusterVersion("1.0")
7 |
8 | def apply(v: aws.model.ClusterVersion): ClusterVersion = new ClusterVersion(
9 | version = v.getClusterVersion,
10 | description = Option(v.getDescription),
11 | parameterGroupFamily = Option(v.getClusterParameterGroupFamily))
12 | }
13 |
14 | case class ClusterVersion(
15 | version: String, description: Option[String] = None, parameterGroupFamily: Option[String] = None)
16 | extends aws.model.ClusterVersion {
17 |
18 | setClusterVersion(version)
19 | setDescription(description.orNull[String])
20 | setClusterParameterGroupFamily(parameterGroupFamily.orNull[String])
21 | }
22 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/EC2SecurityGroup.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | case class EC2SecurityGroup(name: String, ownerId: String, status: String) extends aws.model.EC2SecurityGroup {
6 | setEC2SecurityGroupName(name)
7 | setEC2SecurityGroupOwnerId(ownerId)
8 | setStatus(status)
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/Endpoint.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | object Endpoint {
6 | def apply(e: aws.model.Endpoint): Endpoint = new Endpoint(
7 | address = e.getAddress,
8 | port = e.getPort)
9 | }
10 |
11 | case class Endpoint(address: String, port: Int) extends aws.model.Endpoint {
12 | setAddress(address)
13 | setPort(port)
14 | }
15 |
16 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/Event.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ redshift => aws }
5 |
6 | object Event {
7 |
8 | def apply(e: aws.model.Event): Event = new Event(
9 | sourceIdentifier = e.getSourceIdentifier,
10 | sourceType = aws.model.SourceType.fromValue(e.getSourceType),
11 | message = e.getMessage,
12 | createdAt = new DateTime(e.getDate))
13 | }
14 |
15 | case class Event(
16 | sourceIdentifier: String,
17 | sourceType: aws.model.SourceType,
18 | message: String,
19 | createdAt: DateTime) extends aws.model.Event {
20 |
21 | setDate(createdAt.toDate)
22 | setMessage(message)
23 | setSourceIdentifier(sourceIdentifier)
24 | setSourceType(sourceType)
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/IPRange.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | case class IPRange(cidrip: String, status: String) extends aws.model.IPRange {
6 | setCIDRIP(cidrip)
7 | setStatus(status)
8 | }
9 |
10 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/NewCluster.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import awscala._
4 |
5 | case class NewCluster(
6 | identifier: String,
7 | dbName: String,
8 | masterUsername: String,
9 | masterUserPassword: String,
10 | parameterGroupName: Option[String] = None,
11 | availabilityZone: Option[AvailabilityZone] = None,
12 | subnetGroupName: Option[String] = None,
13 | clusterType: ClusterType = ClusterType.SingleNode,
14 | clusterVersion: ClusterVersion = ClusterVersion.Version_1_0,
15 | nodeType: NodeType = NodeType.dw_hs1_xlarge,
16 | numOfNodes: Int = 1,
17 | port: Int = 5439,
18 | preferredMaintenanceWindow: Option[String] = None,
19 | encrypted: Boolean = false,
20 | allowVersionUpgrade: Boolean = true,
21 | publiclyAccessible: Boolean = true,
22 | automatedSnapshotRetentionPeriod: Int = 1,
23 | securityGroupNames: Seq[String] = Nil,
24 | vpcSecurityGroupIds: Seq[String] = Nil)
25 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/NodeType.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | object NodeType {
4 | val dw_hs1_xlarge = NodeType("dw.hs1.xlarge")
5 | val dw_hs1_8xlarge = NodeType("dw.hs1.8xlarge")
6 | }
7 | case class NodeType(value: String)
8 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/PendingModifiedValues.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | object PendingModifiedValues {
6 | def apply(p: aws.model.PendingModifiedValues): PendingModifiedValues = new PendingModifiedValues(
7 | clusterType = Option(p.getClusterType),
8 | clusterVersion = Option(p.getClusterVersion),
9 | masterUserPassword = Option(p.getMasterUserPassword),
10 | nodeType = Option(p.getNodeType),
11 | numOfNodes = Option[Integer](p.getNumberOfNodes).map(_.asInstanceOf[Int]),
12 | automatedSnapshotRetentionPeriod = Option[Integer](p.getAutomatedSnapshotRetentionPeriod).map(_.asInstanceOf[Int]))
13 | }
14 | case class PendingModifiedValues(
15 | clusterType: Option[String],
16 | clusterVersion: Option[String],
17 | masterUserPassword: Option[String],
18 | nodeType: Option[String],
19 | numOfNodes: Option[Int],
20 | automatedSnapshotRetentionPeriod: Option[Int]) extends aws.model.PendingModifiedValues {
21 |
22 | setAutomatedSnapshotRetentionPeriod(automatedSnapshotRetentionPeriod.map(_.asInstanceOf[Integer]).orNull[Integer])
23 | setClusterType(clusterType.orNull[String])
24 | setClusterVersion(clusterVersion.orNull[String])
25 | setMasterUserPassword(masterUserPassword.orNull[String])
26 | setNodeType(nodeType.orNull[String])
27 | setNumberOfNodes(numOfNodes.map(_.asInstanceOf[Integer]).orNull[Integer])
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/ReservedNode.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.services.{ redshift => aws }
6 |
7 | object ReservedNode {
8 |
9 | def apply(n: aws.model.ReservedNode): ReservedNode = new ReservedNode(
10 | id = n.getReservedNodeId,
11 | state = n.getState,
12 | currencyCode = n.getCurrencyCode,
13 | duration = n.getDuration,
14 | fixedPrice = n.getFixedPrice,
15 | usagePrice = n.getUsagePrice,
16 | nodeCount = n.getNodeCount,
17 | nodeType = NodeType(n.getNodeType),
18 | offeringId = n.getReservedNodeOfferingId,
19 | offeringType = n.getOfferingType,
20 | recurringCharges = n.getRecurringCharges.asScala.map(c => RecurringCharge(
21 | amount = c.getRecurringChargeAmount,
22 | frequency = c.getRecurringChargeFrequency)).toSeq,
23 | startedAt = new DateTime(n.getStartTime))
24 | }
25 |
26 | class ReservedNode(
27 | id: String,
28 | state: String,
29 | currencyCode: String,
30 | duration: Int,
31 | fixedPrice: Double,
32 | usagePrice: Double,
33 | nodeCount: Int,
34 | nodeType: NodeType,
35 | offeringId: String,
36 | offeringType: String,
37 | recurringCharges: Seq[RecurringCharge],
38 | startedAt: DateTime)
39 | extends aws.model.ReservedNode {
40 |
41 | setCurrencyCode(currencyCode)
42 | setDuration(duration)
43 | setFixedPrice(fixedPrice)
44 | setNodeCount(nodeCount)
45 | setNodeType(nodeType.value)
46 | setOfferingType(offeringType)
47 | setRecurringCharges(recurringCharges.map(_.asInstanceOf[aws.model.RecurringCharge]).asJava)
48 | setReservedNodeId(id)
49 | setReservedNodeOfferingId(offeringId)
50 | setStartTime(startedAt.toDate)
51 | setState(state)
52 | setUsagePrice(usagePrice)
53 | }
54 |
55 | case class RecurringCharge(amount: Double, frequency: String) extends aws.model.RecurringCharge {
56 |
57 | setRecurringChargeAmount(amount)
58 | setRecurringChargeFrequency(frequency)
59 | }
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/RestoreStatus.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | object RestoreStatus {
6 | def apply(r: aws.model.RestoreStatus): Option[RestoreStatus] = Option(r).map(r => new RestoreStatus(
7 | status = r.getStatus,
8 | currentRestoreRateInMegaBytesPerSecond = r.getCurrentRestoreRateInMegaBytesPerSecond,
9 | elapsedTimeInSeconds = r.getElapsedTimeInSeconds,
10 | estimatedTimeToCompletionInSeconds = r.getEstimatedTimeToCompletionInSeconds,
11 | progressInMegaBytes = r.getProgressInMegaBytes,
12 | snapshotSizeInMegaBytes = r.getSnapshotSizeInMegaBytes))
13 | }
14 | case class RestoreStatus(
15 | status: String,
16 | currentRestoreRateInMegaBytesPerSecond: Double,
17 | elapsedTimeInSeconds: Long,
18 | estimatedTimeToCompletionInSeconds: Long,
19 | progressInMegaBytes: Long,
20 | snapshotSizeInMegaBytes: Long) extends aws.model.RestoreStatus {
21 |
22 | setCurrentRestoreRateInMegaBytesPerSecond(currentRestoreRateInMegaBytesPerSecond)
23 | setElapsedTimeInSeconds(elapsedTimeInSeconds)
24 | setEstimatedTimeToCompletionInSeconds(estimatedTimeToCompletionInSeconds)
25 | setProgressInMegaBytes(progressInMegaBytes)
26 | setSnapshotSizeInMegaBytes(snapshotSizeInMegaBytes)
27 | setStatus(status)
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/Snapshot.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.services.{ redshift => aws }
6 |
7 | object Snapshot {
8 |
9 | def apply(s: aws.model.Snapshot): Snapshot = new Snapshot(
10 | snapshotIdentifier = s.getSnapshotIdentifier,
11 | clusterIdentifier = s.getClusterIdentifier,
12 | clusterVersion = ClusterVersion(s.getClusterVersion),
13 | dbName = s.getDBName,
14 | port = s.getPort,
15 | masterUsername = s.getMasterUsername,
16 | status = Status(s.getStatus),
17 | availabilityZone = AvailabilityZone(s.getAvailabilityZone),
18 | snapshotType = SnapshotType(s.getSnapshotType),
19 | nodeType = NodeType(s.getNodeType),
20 | numOfNodes = s.getNumberOfNodes,
21 | ownerAccount = s.getOwnerAccount,
22 | encrypted = s.isEncrypted,
23 | elapsedTimeInSeconds = s.getElapsedTimeInSeconds,
24 | estimatedSecondsToCompletion = s.getEstimatedSecondsToCompletion,
25 | actualIncrementalBackupSizeInMegaBytes = s.getActualIncrementalBackupSizeInMegaBytes,
26 | currentBackupRateInMegaBytesPerSecond = s.getCurrentBackupRateInMegaBytesPerSecond,
27 | backupProgressInMegaBytes = s.getBackupProgressInMegaBytes,
28 | totalBackupSizeInMegaBytes = s.getTotalBackupSizeInMegaBytes,
29 | vpcId = s.getVpcId,
30 | accountsWithRestoreAccess = s.getAccountsWithRestoreAccess.asScala.map(a => AccountWithRestoreAccess(a.getAccountId)).toSeq,
31 | clusterCreatedAt = new DateTime(s.getClusterCreateTime),
32 | snapshotCreatedAt = new DateTime(s.getSnapshotCreateTime))
33 | }
34 |
35 | class Snapshot(
36 | val snapshotIdentifier: String,
37 | val clusterIdentifier: String,
38 | val clusterVersion: ClusterVersion,
39 | val dbName: String,
40 | val port: Int,
41 | val masterUsername: String,
42 | val status: Status,
43 | val availabilityZone: AvailabilityZone,
44 | val snapshotType: SnapshotType,
45 | val nodeType: NodeType,
46 | val numOfNodes: Int,
47 | val ownerAccount: String,
48 | val encrypted: Boolean,
49 | val elapsedTimeInSeconds: Long,
50 | val estimatedSecondsToCompletion: Long,
51 | val actualIncrementalBackupSizeInMegaBytes: Double,
52 | val currentBackupRateInMegaBytesPerSecond: Double,
53 | val backupProgressInMegaBytes: Double,
54 | val totalBackupSizeInMegaBytes: Double,
55 | val vpcId: String,
56 | val accountsWithRestoreAccess: Seq[AccountWithRestoreAccess],
57 | val clusterCreatedAt: DateTime,
58 | val snapshotCreatedAt: DateTime) extends aws.model.Snapshot {
59 |
60 | setAccountsWithRestoreAccess(accountsWithRestoreAccess.map(_.asInstanceOf[aws.model.AccountWithRestoreAccess]).asJava)
61 | setActualIncrementalBackupSizeInMegaBytes(actualIncrementalBackupSizeInMegaBytes)
62 | setAvailabilityZone(availabilityZone.name)
63 | setBackupProgressInMegaBytes(backupProgressInMegaBytes)
64 | setClusterCreateTime(clusterCreatedAt.toDate)
65 | setClusterIdentifier(clusterIdentifier)
66 | setClusterVersion(clusterVersion.version)
67 | setCurrentBackupRateInMegaBytesPerSecond(currentBackupRateInMegaBytesPerSecond)
68 | setDBName(dbName)
69 | setElapsedTimeInSeconds(elapsedTimeInSeconds)
70 | setEncrypted(encrypted)
71 | setEstimatedSecondsToCompletion(estimatedSecondsToCompletion)
72 | setMasterUsername(masterUsername)
73 | setNodeType(nodeType.value)
74 | setNumberOfNodes(numOfNodes)
75 | setOwnerAccount(ownerAccount)
76 | setPort(port)
77 | setSnapshotCreateTime(snapshotCreatedAt.toDate)
78 | setSnapshotIdentifier(snapshotIdentifier)
79 | setSnapshotType(snapshotType.value)
80 | setStatus(status.value)
81 | setTotalBackupSizeInMegaBytes(totalBackupSizeInMegaBytes)
82 | setVpcId(vpcId)
83 |
84 | def destroy()(implicit redshift: Redshift) = redshift.delete(this)
85 | }
86 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/SnapshotType.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | object SnapshotType {
4 | val Manual = SnapshotType("manual")
5 | }
6 |
7 | case class SnapshotType(value: String)
8 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/Status.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | object Status {
4 | val Creating = Status("creating")
5 | val Available = Status("available")
6 | val Failed = Status("failed")
7 | val Deleted = Status("deleted")
8 | }
9 |
10 | case class Status(value: String) {
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/Subnet.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 | import awscala._
5 |
6 | object Subnet {
7 |
8 | def apply(s: aws.model.Subnet): Subnet = new Subnet(
9 | identifier = s.getSubnetIdentifier,
10 | availabilityZone = AvailabilityZone(s.getSubnetAvailabilityZone.getName),
11 | status = s.getSubnetStatus)
12 | }
13 |
14 | case class Subnet(identifier: String, availabilityZone: AvailabilityZone, status: String) extends aws.model.Subnet {
15 |
16 | setSubnetAvailabilityZone(new aws.model.AvailabilityZone().withName(availabilityZone.name))
17 | setSubnetIdentifier(identifier)
18 | setSubnetStatus(status)
19 | }
20 |
--------------------------------------------------------------------------------
/redshift/src/main/scala/awscala/redshift/VpcSecurityGroupMembership.scala:
--------------------------------------------------------------------------------
1 | package awscala.redshift
2 |
3 | import com.amazonaws.services.{ redshift => aws }
4 |
5 | object VpcSecurityGroupMembership {
6 | def apply(m: aws.model.VpcSecurityGroupMembership): VpcSecurityGroupMembership = new VpcSecurityGroupMembership(
7 | status = m.getStatus,
8 | vpcSecurityGroupId = m.getVpcSecurityGroupId)
9 | }
10 | case class VpcSecurityGroupMembership(status: String, vpcSecurityGroupId: String) extends aws.model.VpcSecurityGroupMembership {
11 | setStatus(status)
12 | setVpcSecurityGroupId(vpcSecurityGroupId)
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/release.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | sbt +publishSigned sonatypeRelease
4 |
--------------------------------------------------------------------------------
/s3/src/main/scala/awscala/s3/AccessControlList.scala:
--------------------------------------------------------------------------------
1 | package awscala.s3
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.services.{ s3 => aws }
5 |
6 | object AccessControlList {
7 | def apply(acl: aws.model.AccessControlList): AccessControlList = new AccessControlList(acl)
8 | }
9 |
10 | class AccessControlList(acl: aws.model.AccessControlList) extends aws.model.AccessControlList {
11 |
12 | def grants: Set[Grant] = acl.getGrants.asScala.map(g => Grant(g)).toSet
13 | def owner: Owner = Owner(acl.getOwner)
14 | def grantAll(grants: Grant*): Unit = acl.grantAllPermissions(grants: _*)
15 |
16 | def grant(grantee: Grantee, permission: Permission) = acl.grantPermission(grantee, permission)
17 | def revokeAll(grantee: Grantee) = acl.revokeAllPermissions(grantee)
18 | def owner(newOwner: Owner) = acl.setOwner(newOwner)
19 | }
20 |
--------------------------------------------------------------------------------
/s3/src/main/scala/awscala/s3/Bucket.scala:
--------------------------------------------------------------------------------
1 | package awscala.s3
2 |
3 | import java.io.{ InputStream, File }
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.services.{ s3 => aws }
6 | import com.amazonaws.services.s3.model.GetObjectMetadataRequest
7 |
8 | object Bucket {
9 |
10 | def apply(underlying: com.amazonaws.services.s3.model.Bucket): Bucket = {
11 | val bucket = new Bucket(underlying.getName)
12 | bucket.setCreationDate(underlying.getCreationDate)
13 | bucket.setOwner(underlying.getOwner)
14 | bucket
15 | }
16 | }
17 |
18 | case class Bucket(name: String) extends aws.model.Bucket(name) {
19 |
20 | // policy
21 | def policy()(implicit s3: S3) = s3.policy(this)
22 | def policy(text: String)(implicit s3: S3) = s3.policy(this).setPolicyText(text)
23 |
24 | // acl
25 | def acl()(implicit s3: S3) = s3.acl(this)
26 | def acl(acl: AccessControlList)(implicit s3: S3) = s3.bucketAcl(this, acl)
27 |
28 | // object metadata
29 | def getMetadata(key: String)(implicit s3: S3) = s3.getObjectMetadata(name, key)
30 | def getMetadata(key: String, versionId: String)(implicit s3: S3) = s3.getObjectMetadata(new GetObjectMetadataRequest(name, key, versionId))
31 |
32 | // object
33 | def get(key: String)(implicit s3: S3) = getObject(key)
34 | def getObject(key: String)(implicit s3: S3) = s3.get(this, key)
35 | def get(key: String, versionId: String)(implicit s3: S3) = getObject(key, versionId)
36 | def getObject(key: String, versionId: String)(implicit s3: S3) = s3.get(this, key, versionId)
37 |
38 | def keys()(implicit s3: S3) = s3.keys(this)
39 | def keys(prefix: String)(implicit s3: S3) = s3.keys(this, prefix)
40 |
41 | def objectSummaries()(implicit s3: S3) = s3.objectSummaries(this)
42 | def objectSummaries(prefix: String)(implicit s3: S3) = s3.objectSummaries(this, prefix)
43 |
44 | def totalSize()(implicit s3: S3) = objectSummaries().map(_.getSize).sum
45 |
46 | def put(key: String, file: File)(implicit s3: S3) = s3.put(this, key, file)
47 | def putAsPublicRead(key: String, file: File)(implicit s3: S3) = s3.putObjectAsPublicRead(this, key, file)
48 | def putAsPublicReadWrite(key: String, file: File)(implicit s3: S3) = s3.putObjectAsPublicReadWrite(this, key, file)
49 |
50 | def putObject(key: String, file: File)(implicit s3: S3) = s3.putObject(this, key, file)
51 | def putObjectAsPublicRead(key: String, file: File)(implicit s3: S3) = s3.putObjectAsPublicRead(this, key, file)
52 | def putObjectAsPublicReadWrite(key: String, file: File)(implicit s3: S3) = s3.putObjectAsPublicReadWrite(this, key, file)
53 |
54 | // ls
55 | def ls(prefix: String)(implicit s3: S3) = s3.ls(this, prefix)
56 |
57 | // put object from byte array
58 | def putObject(key: String, bytes: Array[Byte], metadata: aws.model.ObjectMetadata)(implicit s3: S3) = s3.putObject(this, key, bytes, metadata)
59 | def putObjectAsPublicRead(key: String, bytes: Array[Byte], metadata: aws.model.ObjectMetadata)(implicit s3: S3) = s3.putObjectAsPublicRead(this, key, bytes, metadata)
60 | def putObjectAsPublicReadWrite(key: String, bytes: Array[Byte], metadata: aws.model.ObjectMetadata)(implicit s3: S3) = s3.putObjectAsPublicReadWrite(this, key, bytes, metadata)
61 |
62 | // put object from input stream
63 | def putObject(key: String, inputStream: InputStream, metadata: aws.model.ObjectMetadata)(implicit s3: S3) = s3.putObject(this, key, inputStream, metadata)
64 | def putObjectAsPublicRead(key: String, inputStream: InputStream, metadata: aws.model.ObjectMetadata)(implicit s3: S3) = s3.putObjectAsPublicRead(this, key, inputStream, metadata)
65 | def putObjectAsPublicReadWrite(key: String, inputStream: InputStream, metadata: aws.model.ObjectMetadata)(implicit s3: S3) = s3.putObjectAsPublicReadWrite(this, key, inputStream, metadata)
66 |
67 | def delete(key: String)(implicit s3: S3) = s3.deleteObject(name, key)
68 | def delete(obj: S3Object)(implicit s3: S3) = s3.deleteObject(obj)
69 | def deleteObject(obj: S3Object)(implicit s3: S3) = s3.deleteObject(obj)
70 | def deleteObjects(objs: Seq[S3Object])(implicit s3: S3) = s3.deleteObjects(objs)
71 |
72 | // configuration
73 | def crossOriginConfig()(implicit s3: S3) = s3.crossOriginConfig(this)
74 | def lifecycleConfig(bucket: Bucket)(implicit s3: S3) = s3.lifecycleConfig(this)
75 | def loggingConfig(bucket: Bucket)(implicit s3: S3) = s3.loggingConfig(this)
76 | def notificationConfig(bucket: Bucket)(implicit s3: S3) = s3.notificationConfig(this)
77 | def taggingConfig(bucket: Bucket)(implicit s3: S3) = s3.taggingConfig(this)
78 | def versioningConfig(bucket: Bucket)(implicit s3: S3) = s3.versioningConfig(this)
79 | def websiteConfig(bucket: Bucket)(implicit s3: S3) = s3.websiteConfig(this)
80 |
81 | def destroy()(implicit s3: S3): Unit = s3.deleteBucket(name)
82 | }
83 |
84 | object BucketPolicy {
85 | def apply(bucket: Bucket, bp: aws.model.BucketPolicy): BucketPolicy = BucketPolicy(bucket, bp.getPolicyText)
86 | }
87 |
88 | case class BucketPolicy(bucket: Bucket, policyText: String) extends aws.model.BucketPolicy {
89 | setPolicyText(policyText)
90 | }
91 |
92 | object BucketCrossOriginConfiguration {
93 | def apply(bucket: Bucket, c: aws.model.BucketCrossOriginConfiguration): BucketCrossOriginConfiguration = {
94 | BucketCrossOriginConfiguration(bucket, c.getRules.asScala.toSeq)
95 | }
96 | }
97 | case class BucketCrossOriginConfiguration(bucket: Bucket, rules: Seq[aws.model.CORSRule])
98 | extends aws.model.BucketCrossOriginConfiguration(rules.asJava) {
99 |
100 | def destroy()(implicit s3: S3) = s3.deleteCrossOriginConfig(bucket)
101 | }
102 |
103 | object BucketLifecycleConfiguration {
104 | def apply(bucket: Bucket, c: aws.model.BucketLifecycleConfiguration): BucketLifecycleConfiguration = {
105 | BucketLifecycleConfiguration(bucket, c.getRules.asScala.toSeq)
106 | }
107 | }
108 | case class BucketLifecycleConfiguration(bucket: Bucket, rules: Seq[aws.model.BucketLifecycleConfiguration.Rule])
109 | extends aws.model.BucketLifecycleConfiguration(rules.asJava) {
110 |
111 | def destroy()(implicit s3: S3) = s3.deleteLifecycleConfig(bucket)
112 | }
113 |
114 | object BucketLoggingConfiguration {
115 | def apply(c: aws.model.BucketLoggingConfiguration): BucketLoggingConfiguration = {
116 | BucketLoggingConfiguration(Bucket(c.getDestinationBucketName), c.getLogFilePrefix)
117 | }
118 | }
119 | case class BucketLoggingConfiguration(bucket: Bucket, logFilePrefix: String)
120 | extends aws.model.BucketLoggingConfiguration(bucket.name, logFilePrefix)
121 |
122 | object BucketNotificationConfiguration {
123 | def apply(bucket: Bucket, c: aws.model.BucketNotificationConfiguration): BucketNotificationConfiguration = {
124 | BucketNotificationConfiguration(bucket, c.getTopicConfigurations.asScala.toSeq)
125 | }
126 | }
127 | case class BucketNotificationConfiguration(
128 | bucket: Bucket, topicConfigs: Seq[aws.model.BucketNotificationConfiguration.TopicConfiguration])
129 | extends aws.model.BucketNotificationConfiguration(topicConfigs.asJava)
130 |
131 | object BucketTaggingConfiguration {
132 | def apply(bucket: Bucket, c: aws.model.BucketTaggingConfiguration): BucketTaggingConfiguration = {
133 | BucketTaggingConfiguration(bucket, c.getAllTagSets.asScala.toSeq)
134 | }
135 | }
136 | case class BucketTaggingConfiguration(bucket: Bucket, tagSets: Seq[aws.model.TagSet])
137 | extends aws.model.BucketTaggingConfiguration(tagSets.asJava) {
138 |
139 | def destroy()(implicit s3: S3) = s3.deleteTaggingConfig(bucket)
140 | }
141 |
142 | object BucketVersioningConfiguration {
143 | def apply(bucket: Bucket, c: aws.model.BucketVersioningConfiguration): BucketVersioningConfiguration = {
144 | BucketVersioningConfiguration(bucket, c.getStatus, c.isMfaDeleteEnabled)
145 | }
146 | }
147 | case class BucketVersioningConfiguration(bucket: Bucket, status: String, mfaDeleteEnabled: Boolean)
148 | extends aws.model.BucketVersioningConfiguration {
149 | setStatus(status)
150 | setMfaDeleteEnabled(mfaDeleteEnabled)
151 | }
152 |
153 | object BucketWebsiteConfiguration {
154 | def apply(bucket: Bucket, c: aws.model.BucketWebsiteConfiguration): BucketWebsiteConfiguration = {
155 | BucketWebsiteConfiguration(bucket, c.getIndexDocumentSuffix, c.getErrorDocument, c.getRoutingRules.asScala.toSeq)
156 | }
157 | }
158 | case class BucketWebsiteConfiguration(
159 | bucket: Bucket, indexDocumentSuffix: String, errorDocument: String, routingRules: Seq[aws.model.RoutingRule])
160 | extends aws.model.BucketWebsiteConfiguration(indexDocumentSuffix, errorDocument) {
161 | setRoutingRules(routingRules.asJava)
162 |
163 | def destroy()(implicit s3: S3) = s3.deleteWebsiteConfig(bucket)
164 | }
165 |
166 |
--------------------------------------------------------------------------------
/s3/src/main/scala/awscala/s3/Grant.scala:
--------------------------------------------------------------------------------
1 | package awscala.s3
2 |
3 | import com.amazonaws.services.{ s3 => aws }
4 |
5 | object Grant {
6 | def apply(g: aws.model.Grant): Grant = Grant(Grantee(g.getGrantee), g.getPermission)
7 | }
8 | case class Grant(grantee: Grantee, permission: Permission) extends aws.model.Grant(grantee, permission)
9 |
10 |
--------------------------------------------------------------------------------
/s3/src/main/scala/awscala/s3/Grantee.scala:
--------------------------------------------------------------------------------
1 | package awscala.s3
2 |
3 | import com.amazonaws.services.{ s3 => aws }
4 |
5 | object Grantee {
6 | def apply(g: aws.model.Grantee): Grantee = Grantee(g.getIdentifier, g.getTypeIdentifier)
7 | }
8 | case class Grantee(identifier: String, typeIdentifier: String) extends aws.model.Grantee {
9 | override def getIdentifier = identifier
10 | override def getTypeIdentifier = typeIdentifier
11 | override def setIdentifier(id: String) = throw new UnsupportedOperationException
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/s3/src/main/scala/awscala/s3/Owner.scala:
--------------------------------------------------------------------------------
1 | package awscala.s3
2 |
3 | import com.amazonaws.services.{ s3 => aws }
4 |
5 | object Owner {
6 | def apply(o: aws.model.Owner) = new Owner(o.getId, o.getDisplayName)
7 | }
8 |
9 | case class Owner(id: String, displayName: String) extends aws.model.Owner(id, displayName)
10 |
11 |
--------------------------------------------------------------------------------
/s3/src/main/scala/awscala/s3/PutObjectResult.scala:
--------------------------------------------------------------------------------
1 | package awscala.s3
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ s3 => aws }
5 |
6 | object PutObjectResult {
7 |
8 | def apply(bucket: Bucket, key: String, obj: aws.model.CopyObjectResult): PutObjectResult = new PutObjectResult(
9 | bucket = bucket,
10 | key = key,
11 | versionId = obj.getVersionId,
12 | eTag = obj.getETag,
13 | contentMd5 = null,
14 | expirationTime = new DateTime(obj.getExpirationTime),
15 | expirationTimeRuleId = obj.getExpirationTimeRuleId,
16 | sseAlgorithm = obj.getSSEAlgorithm)
17 |
18 | def apply(bucket: Bucket, key: String, obj: aws.model.PutObjectResult): PutObjectResult = new PutObjectResult(
19 | bucket = bucket,
20 | key = key,
21 | versionId = obj.getVersionId,
22 | eTag = obj.getETag,
23 | contentMd5 = obj.getContentMd5,
24 | expirationTime = new DateTime(obj.getExpirationTime),
25 | expirationTimeRuleId = obj.getExpirationTimeRuleId,
26 | sseAlgorithm = obj.getSSEAlgorithm)
27 | }
28 |
29 | case class PutObjectResult(bucket: Bucket, key: String, versionId: String,
30 | eTag: String, contentMd5: String, expirationTime: DateTime, expirationTimeRuleId: String, sseAlgorithm: String)
31 | extends aws.model.PutObjectResult {
32 |
33 | @deprecated("Use #sseAlgorithm instead", "0.3.0")
34 | def serverSideEncryption = sseAlgorithm
35 |
36 | setVersionId(versionId)
37 | setETag(eTag)
38 | setContentMd5(contentMd5)
39 | setExpirationTime(expirationTime.toDate)
40 | setExpirationTimeRuleId(expirationTimeRuleId)
41 | setSSEAlgorithm(sseAlgorithm)
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/s3/src/main/scala/awscala/s3/S3Object.scala:
--------------------------------------------------------------------------------
1 | package awscala.s3
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ s3 => aws }
5 |
6 | object S3Object {
7 |
8 | def apply(bucket: Bucket, obj: aws.model.S3Object): S3Object = new S3Object(
9 | bucket = bucket,
10 | key = obj.getKey,
11 | content = obj.getObjectContent,
12 | redirectLocation = obj.getRedirectLocation,
13 | metadata = obj.getObjectMetadata)
14 | }
15 |
16 | case class S3Object(
17 | bucket: Bucket, key: String, content: java.io.InputStream = null,
18 | redirectLocation: String = null, metadata: aws.model.ObjectMetadata = null)
19 | extends aws.model.S3Object {
20 |
21 | setBucketName(bucket.name)
22 | setKey(key)
23 | setObjectContent(content)
24 | setRedirectLocation(redirectLocation)
25 | setObjectMetadata(metadata)
26 |
27 | import aws.model.{ CannedAccessControlList => CannedACL }
28 |
29 | def setAsAuthenticatedRead()(implicit s3: S3) = s3.acl(this, CannedACL.AuthenticatedRead)
30 | def setAsBucketOwnerFullControl()(implicit s3: S3) = s3.acl(this, CannedACL.BucketOwnerFullControl)
31 | def setAsBucketOwnerRead()(implicit s3: S3) = s3.acl(this, CannedACL.BucketOwnerRead)
32 | def setAsLogDeliveryWrite()(implicit s3: S3) = s3.acl(this, CannedACL.LogDeliveryWrite)
33 | def setAsPrivate()(implicit s3: S3) = s3.acl(this, CannedACL.Private)
34 | def setAsPublicRead()(implicit s3: S3) = s3.acl(this, CannedACL.PublicRead)
35 | def setAsPublicReadWrite()(implicit s3: S3) = s3.acl(this, CannedACL.PublicReadWrite)
36 |
37 | def publicUrl: java.net.URL = new java.net.URL(s"http://${bucket.name}.s3.amazonaws.com/${key}")
38 |
39 | def generatePresignedUrl(expiration: DateTime)(implicit s3: S3): java.net.URL = {
40 | s3.generatePresignedUrl(this, expiration)
41 | }
42 |
43 | lazy val versionId: String = Option(metadata).map(_.getVersionId).getOrElse(null)
44 |
45 | def destroy()(implicit s3: S3): Unit = s3.deleteObject(this)
46 | }
47 |
--------------------------------------------------------------------------------
/s3/src/main/scala/awscala/s3/S3ObjectSummary.scala:
--------------------------------------------------------------------------------
1 | package awscala.s3
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ s3 => aws }
5 |
6 | object S3ObjectSummary {
7 |
8 | def apply(bucket: Bucket, obj: aws.model.S3ObjectSummary): S3ObjectSummary = new S3ObjectSummary(
9 | bucket = bucket,
10 | key = obj.getKey,
11 | size = obj.getSize,
12 | storageClass = obj.getStorageClass,
13 | eTag = obj.getETag,
14 | lastModified = new DateTime(obj.getLastModified),
15 | owner = obj.getOwner)
16 | }
17 |
18 | class S3ObjectSummary(val bucket: Bucket, key: String, size: Long,
19 | storageClass: String, eTag: String, lastModified: DateTime, owner: aws.model.Owner)
20 | extends aws.model.S3ObjectSummary {
21 |
22 | setBucketName(bucket.name)
23 | setKey(key)
24 | setSize(size)
25 | setStorageClass(storageClass)
26 | setETag(eTag)
27 | setLastModified(lastModified.toDate)
28 | setOwner(owner)
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/s3/src/main/scala/awscala/s3/package.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | package object s3 {
4 |
5 | type Permission = com.amazonaws.services.s3.model.Permission
6 |
7 | type CannedAccessControlList = com.amazonaws.services.s3.model.CannedAccessControlList
8 |
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/s3/src/test/scala/awscala/S3Spec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import awscala._, s3._
4 |
5 | import org.slf4j._
6 | import org.scalatest._
7 | import org.scalatest.flatspec.AnyFlatSpec
8 | import org.scalatest.matchers.should.Matchers
9 |
10 | class S3Spec extends AnyFlatSpec with Matchers {
11 |
12 | behavior of "S3"
13 |
14 | val log = LoggerFactory.getLogger(this.getClass)
15 |
16 | it should "handle buckets with > 1000 objects in them " in {
17 | implicit val s3 = S3.at(Region.Tokyo)
18 |
19 | // buckets
20 | val buckets = s3.buckets
21 | log.info(s"Buckets: ${buckets}")
22 |
23 | val newBucketName = s"awscala-unit-test-${System.currentTimeMillis}"
24 | val bucket = s3.createBucket(newBucketName)
25 | log.info(s"Created Bucket: ${bucket}")
26 |
27 | // create/update objects
28 | val file = new java.io.File("s3/src/main/scala/awscala/s3/S3.scala")
29 | for (i <- 1 to 1002) {
30 | bucket.put("S3.scala-" + i, file)
31 | }
32 |
33 | // delete objects
34 | val summaries = bucket.objectSummaries().toList
35 |
36 | summaries foreach {
37 | o => { log.info("deleting ${o.getKey}"); s3.deleteObject(bucket.name, o.getKey) }
38 | }
39 | bucket.destroy()
40 | }
41 |
42 | it should "provide cool APIs" in {
43 | implicit val s3 = S3.at(Region.Tokyo)
44 |
45 | // buckets
46 | val buckets = s3.buckets
47 | log.info(s"Buckets: ${buckets}")
48 |
49 | val newBucketName = s"awscala-unit-test-${System.currentTimeMillis}"
50 | val bucket = s3.createBucket(newBucketName)
51 | log.info(s"Created Bucket: ${bucket}")
52 |
53 | // create/update objects
54 | bucket.put("S3.scala", new java.io.File("s3/src/main/scala/awscala/s3/S3.scala"))
55 | bucket.putAsPublicRead("S3.scala", new java.io.File("s3/src/main/scala/awscala/s3/S3.scala"))
56 | bucket.put("S3Spec.scala", new java.io.File("src/test/scala/awscala/S3Spec.scala"))
57 |
58 | // get objects
59 | val s3obj: Option[S3Object] = bucket.get("S3.scala")
60 | log.info(s"Object: ${s3obj}")
61 | val summaries = bucket.objectSummaries()
62 | log.info(s"Object Summaries: ${summaries}")
63 |
64 | // delete objects
65 | s3obj.foreach(o => { o.content.close(); bucket.delete(o) })
66 | bucket.get("S3Spec.scala").foreach { o => o.content.close(); o.destroy() } // working with implicit S3 instance
67 |
68 | bucket.destroy()
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/simpledb/src/main/scala/awscala/simpledb/Attribute.scala:
--------------------------------------------------------------------------------
1 | package awscala.simpledb
2 |
3 | import com.amazonaws.services.{ simpledb => aws }
4 |
5 | object Attribute {
6 |
7 | def apply(item: Item, a: aws.model.Attribute): Attribute = new Attribute(
8 | item = item,
9 | name = a.getName,
10 | value = a.getValue,
11 | alternateNameEncoding = a.getAlternateNameEncoding,
12 | alternateValueEncoding = a.getAlternateValueEncoding)
13 | }
14 |
15 | case class Attribute(
16 | item: Item, name: String, value: String, alternateNameEncoding: String, alternateValueEncoding: String)
17 | extends aws.model.Attribute {
18 |
19 | setAlternateNameEncoding(alternateNameEncoding)
20 | setAlternateValueEncoding(alternateValueEncoding)
21 | setName(name)
22 | setValue(value)
23 |
24 | def update(newValue: String)(implicit simpleDB: SimpleDB): Attribute = {
25 | simpleDB.replaceAttributesIfExists(item, name -> newValue)
26 | simpleDB.attributes(item).find(_.name == name).get
27 | }
28 |
29 | def destroy()(implicit simpleDB: SimpleDB): Unit = {
30 | simpleDB.deleteAttributes(Seq(this))
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/simpledb/src/main/scala/awscala/simpledb/Domain.scala:
--------------------------------------------------------------------------------
1 | package awscala.simpledb
2 |
3 | case class Domain(name: String) {
4 |
5 | def metadata()(implicit simpleDB: SimpleDB): DomainMetadata = simpleDB.domainMetadata(this)
6 |
7 | def select(expression: String, consistentRead: Boolean = true)(implicit simpleDB: SimpleDB): Seq[Item] = {
8 | simpleDB.select(this, expression, consistentRead)
9 | }
10 |
11 | def replaceIfExists(itemName: String, attributes: (String, String)*)(implicit simpleDB: SimpleDB): Unit = {
12 | simpleDB.replaceAttributesIfExists(Item(this, itemName), attributes: _*)
13 | }
14 |
15 | def put(itemName: String, attributes: (String, String)*)(implicit simpleDB: SimpleDB): Unit = {
16 | simpleDB.putAttributes(Item(this, itemName), attributes: _*)
17 | }
18 |
19 | def delete(item: Item)(implicit simpleDB: SimpleDB) = deleteItem(item)
20 | def deleteItem(item: Item)(implicit simpleDB: SimpleDB) = simpleDB.deleteItems(Seq(item))
21 |
22 | def delete(attribute: Attribute)(implicit simpleDB: SimpleDB) = deleteAttribute(attribute)
23 | def deleteAttribute(attribute: Attribute)(implicit simpleDB: SimpleDB) = simpleDB.deleteAttributes(Seq(attribute))
24 |
25 | def deleteItems(items: Seq[Item])(implicit simpleDB: SimpleDB) = simpleDB.deleteItems(items)
26 | def deleteAttributes(attributes: Seq[Attribute])(implicit simpleDB: SimpleDB) = simpleDB.deleteAttributes(attributes)
27 |
28 | def destroy()(implicit simpleDB: SimpleDB): Unit = simpleDB.deleteDomain(this)
29 |
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/simpledb/src/main/scala/awscala/simpledb/DomainMetadata.scala:
--------------------------------------------------------------------------------
1 | package awscala.simpledb
2 |
3 | import com.amazonaws.services.{ simpledb => aws }
4 |
5 | object DomainMetadata {
6 |
7 | def apply(r: aws.model.DomainMetadataResult): DomainMetadata = new DomainMetadata(
8 | attributeNameCount = r.getAttributeNameCount,
9 | attributeNamesSizeBytes = r.getAttributeNamesSizeBytes,
10 | attributeValueCount = r.getAttributeValueCount,
11 | attributeValuesSizeBytes = r.getAttributeValuesSizeBytes,
12 | itemCount = r.getItemCount,
13 | itemNamesSizeBytes = r.getItemNamesSizeBytes,
14 | timestamp = r.getTimestamp)
15 | }
16 | case class DomainMetadata(
17 | attributeNameCount: Int, attributeNamesSizeBytes: Long,
18 | attributeValueCount: Int, attributeValuesSizeBytes: Long,
19 | itemCount: Int, itemNamesSizeBytes: Long,
20 | timestamp: Int)
21 |
22 |
--------------------------------------------------------------------------------
/simpledb/src/main/scala/awscala/simpledb/Item.scala:
--------------------------------------------------------------------------------
1 | package awscala.simpledb
2 |
3 | import scala.jdk.CollectionConverters._
4 | import com.amazonaws.services.{ simpledb => aws }
5 |
6 | object Item {
7 |
8 | def apply(domain: Domain, name: String) = new Item(
9 | domain = domain, name = name)
10 |
11 | def apply(domain: Domain, i: aws.model.Item): Item = {
12 | val item = new Item(
13 | domain = domain,
14 | name = i.getName,
15 | alternateNameEncoding = Option(i.getAlternateNameEncoding),
16 | attributes = Nil)
17 | item.copy(attributes = i.getAttributes.asScala.map(a => Attribute(item, a)).toSeq)
18 | }
19 | }
20 |
21 | case class Item(domain: Domain, name: String, alternateNameEncoding: Option[String] = None, attributes: Seq[Attribute] = Nil)
22 | extends aws.model.Item {
23 |
24 | setAlternateNameEncoding(alternateNameEncoding.orNull[String])
25 | setAttributes(attributes.map(_.asInstanceOf[aws.model.Attribute]).asJavaCollection)
26 | setName(name)
27 |
28 | def replaceAttributesIfExists(attributes: (String, String)*)(implicit simpleDB: SimpleDB): Unit = {
29 | simpleDB.replaceAttributesIfExists(this, attributes: _*)
30 | }
31 |
32 | def put(attributes: (String, String)*)(implicit simpleDB: SimpleDB): Unit = putAttributes(attributes: _*)
33 | def putAttributes(attributes: (String, String)*)(implicit simpleDB: SimpleDB): Unit = {
34 | simpleDB.putAttributes(this, attributes: _*)
35 | }
36 |
37 | def delete(attributes: Seq[Attribute])(implicit simpleDB: SimpleDB): Unit = deleteAttributes(attributes)
38 | def deleteAttributes(attributes: Seq[Attribute])(implicit simpleDB: SimpleDB): Unit = {
39 | simpleDB.deleteAttributes(attributes)
40 | }
41 |
42 | def destroy()(implicit simpleDB: SimpleDB): Unit = simpleDB.deleteItems(Seq(this))
43 |
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/simpledb/src/main/scala/awscala/simpledb/SimpleDB.scala:
--------------------------------------------------------------------------------
1 | package awscala.simpledb
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.auth.AWSCredentialsProvider
6 | import com.amazonaws.services.{ simpledb => aws }
7 | import com.amazonaws.services.simpledb.model.ListDomainsRequest
8 |
9 | object SimpleDB {
10 |
11 | def apply(credentials: Credentials)(implicit region: Region): SimpleDB = new SimpleDBClient(BasicCredentialsProvider(credentials.getAWSAccessKeyId, credentials.getAWSSecretKey)).at(region)
12 | def apply(credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load())(implicit region: Region = Region.default()): SimpleDB = new SimpleDBClient(credentialsProvider).at(region)
13 | def apply(accessKeyId: String, secretAccessKey: String)(implicit region: Region): SimpleDB = apply(BasicCredentialsProvider(accessKeyId, secretAccessKey)).at(region)
14 |
15 | def at(region: Region): SimpleDB = apply()(region)
16 | }
17 |
18 | /**
19 | * Amazon SimpleDB Java client wrapper
20 | * @see [[http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/]]
21 | */
22 | trait SimpleDB extends aws.AmazonSimpleDB {
23 |
24 | def at(region: Region): SimpleDB = {
25 | this.setRegion(region)
26 | this
27 | }
28 |
29 | // ------------------------------------------
30 | // Domains
31 | // ------------------------------------------
32 |
33 | def domains: Seq[Domain] = {
34 | import aws.model.ListDomainsResult
35 |
36 | object domainsSequencer extends Sequencer[Domain, ListDomainsResult, String] {
37 | val baseRequest = new ListDomainsRequest()
38 | def getInitial = listDomains(baseRequest)
39 | def getMarker(r: ListDomainsResult) = r.getNextToken()
40 | def getFromMarker(marker: String) = listDomains(baseRequest.withNextToken(marker))
41 | def getList(r: ListDomainsResult) = (r.getDomainNames().asScala.toList map { x => Domain(x) }).asJava
42 | }
43 | domainsSequencer.sequence
44 | }
45 |
46 | def domain(name: String): Option[Domain] = domains.find(_.name == name)
47 |
48 | def domainMetadata(domain: Domain): DomainMetadata = {
49 | DomainMetadata(domainMetadata(new aws.model.DomainMetadataRequest().withDomainName(domain.name)))
50 | }
51 |
52 | def createDomain(name: String): Domain = {
53 | createDomain(new aws.model.CreateDomainRequest().withDomainName(name))
54 | Domain(name)
55 | }
56 |
57 | def deleteDomain(domain: Domain): Unit = deleteDomain(new aws.model.DeleteDomainRequest().withDomainName(domain.name))
58 |
59 | // ------------------------------------------
60 | // Items/Attributes
61 | // ------------------------------------------
62 |
63 | def select(domain: Domain, expression: String, consistentRead: Boolean = false): Seq[Item] = {
64 | import aws.model.SelectResult
65 |
66 | object selectSequencer extends Sequencer[Item, SelectResult, String] {
67 | val baseRequest = new aws.model.SelectRequest().withSelectExpression(expression).withConsistentRead(consistentRead)
68 | def getInitial = select(baseRequest)
69 | def getMarker(r: SelectResult) = r.getNextToken()
70 | def getFromMarker(marker: String) = select(baseRequest.withNextToken(marker))
71 | def getList(r: SelectResult) = (r.getItems().asScala.toList map { x => Item(domain, x) }).asJava
72 | }
73 | selectSequencer.sequence
74 | }
75 |
76 | def attributes(item: Item): Seq[Attribute] = {
77 | getAttributes(
78 | new aws.model.GetAttributesRequest().withDomainName(item.domain.name).withItemName(item.name))
79 | .getAttributes.asScala.map(as => Attribute(item, as)).toSeq
80 | }
81 |
82 | def replaceAttributesIfExists(item: Item, attributes: (String, String)*): Unit = {
83 | putAttributes(new aws.model.PutAttributesRequest()
84 | .withDomainName(item.domain.name)
85 | .withItemName(item.name)
86 | .withAttributes(attributes.map {
87 | case (k, v) =>
88 | new aws.model.ReplaceableAttribute().withName(k).withValue(v).withReplace(true)
89 | }.asJava))
90 | }
91 |
92 | def putAttributes(item: Item, attributes: (String, String)*): Unit = {
93 | putAttributes(new aws.model.PutAttributesRequest()
94 | .withDomainName(item.domain.name)
95 | .withItemName(item.name)
96 | .withAttributes(attributes.map {
97 | case (k, v) =>
98 | new aws.model.ReplaceableAttribute().withName(k).withValue(v).withReplace(false)
99 | }.asJava))
100 | }
101 |
102 | def deleteItems(items: Seq[Item]): Unit = {
103 | items.headOption.foreach { item =>
104 | batchDeleteAttributes(new aws.model.BatchDeleteAttributesRequest()
105 | .withDomainName(item.domain.name)
106 | .withItems(items.map(i => new aws.model.DeletableItem().withName(i.name)).asJava))
107 | }
108 | }
109 |
110 | def deleteAttributes(attributes: Seq[Attribute]): Unit = {
111 | attributes.headOption.foreach { attr =>
112 | deleteAttributes(new aws.model.DeleteAttributesRequest()
113 | .withItemName(attr.item.name)
114 | .withAttributes(attributes.map(_.asInstanceOf[aws.model.Attribute]).asJava))
115 | }
116 | }
117 |
118 | }
119 |
120 | /**
121 | * Default Implementation
122 | *
123 | * @param credentialsProvider credentialsProvider
124 | */
125 | class SimpleDBClient(credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load())
126 | extends aws.AmazonSimpleDBClient(credentialsProvider)
127 | with SimpleDB
128 |
129 |
--------------------------------------------------------------------------------
/simpledb/src/test/scala/awscala/SimpleDBSpec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import awscala._, simpledb._
4 |
5 | import org.slf4j._
6 | import org.scalatest._
7 | import org.scalatest.flatspec.AnyFlatSpec
8 | import org.scalatest.matchers.should.Matchers
9 |
10 | class SimpleDBSpec extends AnyFlatSpec with Matchers {
11 |
12 | behavior of "SimpleDB"
13 |
14 | val log = LoggerFactory.getLogger(this.getClass)
15 |
16 | it should "provide cool APIs" in {
17 | implicit val simpleDB = SimpleDB.at(Region.Tokyo)
18 |
19 | val domainName = s"awscala_unittest_${System.currentTimeMillis}"
20 | val domain: Domain = simpleDB.createDomain(domainName)
21 | log.info(s"Created Domain: ${domain}")
22 |
23 | domain.put("User-001", "name" -> "Alice", "age" -> "23", "country" -> "America")
24 | domain.put("User-002", "name" -> "Bob", "age" -> "34", "country" -> "America")
25 | domain.put("User-003", "name" -> "Chris", "age" -> "27", "country" -> "Japan")
26 |
27 | val items: Seq[Item] = domain.select(s"select * from ${domainName} where country = 'America'")
28 | log.info(s"Found Items: ${items}")
29 | items.size should equal(2)
30 |
31 | items.foreach(_.destroy())
32 | simpleDB.domains.foreach(_.destroy())
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/sqs/src/main/scala/awscala/sqs/DeleteMessageBatchEntry.scala:
--------------------------------------------------------------------------------
1 | package awscala.sqs
2 |
3 | case class DeleteMessageBatchEntry(id: String, receiptHandle: String)
4 | extends com.amazonaws.services.sqs.model.DeleteMessageBatchRequestEntry(id, receiptHandle)
5 |
6 |
--------------------------------------------------------------------------------
/sqs/src/main/scala/awscala/sqs/Message.scala:
--------------------------------------------------------------------------------
1 | package awscala.sqs
2 |
3 | import com.amazonaws.services.sqs.model.MessageAttributeValue
4 | import scala.jdk.CollectionConverters._
5 |
6 | object Message {
7 |
8 | def apply(queue: Queue, msg: com.amazonaws.services.sqs.model.Message) = new Message(
9 | queue = queue,
10 | id = msg.getMessageId,
11 | body = msg.getBody,
12 | receiptHandle = msg.getReceiptHandle,
13 | attributes = msg.getAttributes.asScala.toMap,
14 | messageAttributes = msg.getMessageAttributes.asScala.toMap)
15 | }
16 |
17 | case class Message(
18 | queue: Queue,
19 | id: String,
20 | body: String,
21 | receiptHandle: String,
22 | attributes: Map[String, String],
23 | messageAttributes: Map[String, MessageAttributeValue] = Map())
24 | extends com.amazonaws.services.sqs.model.Message {
25 |
26 | setMessageId(id)
27 | setBody(body)
28 | setReceiptHandle(receiptHandle)
29 | setAttributes(attributes.asJava)
30 | setMessageAttributes(messageAttributes.asJava)
31 |
32 | def destroy()(implicit sqs: SQS): Unit = sqs.deleteMessage(this)
33 |
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/sqs/src/main/scala/awscala/sqs/MessageBatchEntry.scala:
--------------------------------------------------------------------------------
1 | package awscala.sqs
2 |
3 | case class MessageBatchEntry(id: String, messageBody: String)
4 | extends com.amazonaws.services.sqs.model.SendMessageBatchRequestEntry(id, messageBody)
5 |
6 |
--------------------------------------------------------------------------------
/sqs/src/main/scala/awscala/sqs/Queue.scala:
--------------------------------------------------------------------------------
1 | package awscala.sqs
2 |
3 | case class Queue(url: String) {
4 |
5 | def messages()(implicit sqs: SQS): Seq[Message] = sqs.receiveMessage(this)
6 |
7 | def add(messages: String*)(implicit sqs: SQS) = sqs.sendMessages(this, messages)
8 | def addAll(messages: Seq[String])(implicit sqs: SQS) = sqs.sendMessages(this, messages)
9 |
10 | def remove(messages: Message*)(implicit sqs: SQS) = sqs.deleteMessages(messages)
11 | def removeAll(messages: Seq[Message])(implicit sqs: SQS) = sqs.deleteMessages(messages)
12 |
13 | def destroy()(implicit sqs: SQS) = sqs.deleteQueue(this)
14 |
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/sqs/src/main/scala/awscala/sqs/SQS.scala:
--------------------------------------------------------------------------------
1 | package awscala.sqs
2 |
3 | import awscala._
4 | import scala.jdk.CollectionConverters._
5 | import com.amazonaws.services.{ sqs => aws }
6 | import com.amazonaws.auth.{ AWSCredentialsProvider, AWSSessionCredentials }
7 |
8 | object SQS {
9 |
10 | def apply(credentials: Credentials)(implicit region: Region): SQS = new SQSClient(BasicCredentialsProvider(credentials.getAWSAccessKeyId, credentials.getAWSSecretKey)).at(region)
11 | def apply(credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load())(implicit region: Region = Region.default()): SQS = new SQSClient(credentialsProvider).at(region)
12 | def apply(accessKeyId: String, secretAccessKey: String)(implicit region: Region): SQS = apply(BasicCredentialsProvider(accessKeyId, secretAccessKey)).at(region)
13 |
14 | def at(region: Region): SQS = apply()(region)
15 | }
16 |
17 | /**
18 | * Amazon Simple Queue Service Java client wrapper
19 | * @see [[http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/]]
20 | */
21 | trait SQS extends aws.AmazonSQS {
22 |
23 | def at(region: Region): SQS = {
24 | this.setRegion(region)
25 | this
26 | }
27 |
28 | // ------------------------------------------
29 | // Queues
30 | // ------------------------------------------
31 |
32 | // createQueue is added since SDK 1.7.x
33 | def createQueueAndReturnQueueName(name: String): Queue = {
34 | val result = createQueue(new aws.model.CreateQueueRequest(name))
35 | Queue(result.getQueueUrl)
36 | }
37 | def delete(queue: Queue): Unit = deleteQueue(queue)
38 | def deleteQueue(queue: Queue): Unit = deleteQueue(new aws.model.DeleteQueueRequest(queue.url))
39 |
40 | def queues: Seq[Queue] = listQueues().getQueueUrls.asScala.map(url => Queue(url)).toSeq
41 |
42 | def queuesByNamePrefix(name: String): Seq[Queue] = listQueues(name).getQueueUrls.asScala.map(url => Queue(url)).toSeq
43 |
44 | def queue(name: String): Option[Queue] = queuesByNamePrefix(name).find(_.url.split("/").last == name)
45 |
46 | def queueUrl(name: String): Option[String] = try {
47 | Some(getQueueUrl(new aws.model.GetQueueUrlRequest(name)).getQueueUrl)
48 | } catch {
49 | case e: aws.model.QueueDoesNotExistException => None
50 | }
51 |
52 | def withQueue[A](queue: Queue)(op: (SQSClientWithQueue) => A): A = op(new SQSClientWithQueue(this, queue))
53 |
54 | def queueAttributes(queue: Queue, attributeName: String): Map[String, String] = {
55 | val result = getQueueAttributes(new aws.model.GetQueueAttributesRequest(queue.url, List(attributeName).asJava))
56 | result.getAttributes.asScala.toMap
57 | }
58 |
59 | // ------------------------------------------
60 | // Messages
61 | // ------------------------------------------
62 |
63 | def send(queue: Queue, messageBody: String): aws.model.SendMessageResult = sendMessage(queue, messageBody)
64 | def sendMessage(queue: Queue, messageBody: String): aws.model.SendMessageResult = {
65 | sendMessage(new aws.model.SendMessageRequest(queue.url, messageBody))
66 | }
67 | def sendMessageWithDelaySeconds(queue: Queue, messageBody: String, delaySeconds: Int): aws.model.SendMessageResult = {
68 | sendMessage(new aws.model.SendMessageRequest(queue.url, messageBody).withDelaySeconds(delaySeconds))
69 | }
70 | def sendMessages(queue: Queue, messageBodies: Seq[String]): aws.model.SendMessageBatchResult = {
71 | val batchId = s"${Thread.currentThread.getId}-${System.nanoTime}"
72 | sendMessageBatch(queue, messageBodies.zipWithIndex.map { case (body, idx) => new MessageBatchEntry(s"${batchId}-${idx}", body) })
73 | }
74 | def sendMessageBatch(queue: Queue, messages: Seq[MessageBatchEntry]): aws.model.SendMessageBatchResult = {
75 | sendMessageBatch(new aws.model.SendMessageBatchRequest(
76 | queue.url,
77 | messages.map(_.asInstanceOf[aws.model.SendMessageBatchRequestEntry]).asJava))
78 | }
79 |
80 | def receive(queue: Queue): Seq[Message] = receiveMessage(queue)
81 | def receiveMessage(queue: Queue): Seq[Message] = receiveMessage(queue, 10)
82 | def receiveMessage(queue: Queue, count: Int = 10, wait: Int = 0, requestCredentials: Option[AWSSessionCredentials] = None): Seq[Message] = {
83 | val req = new aws.model.ReceiveMessageRequest(queue.url).withMaxNumberOfMessages(count).withWaitTimeSeconds(wait)
84 | requestCredentials.foreach(c => req.setRequestCredentials(c))
85 | receiveMessage(req).getMessages.asScala.map(msg => Message(queue, msg)).toSeq
86 | }
87 |
88 | def delete(message: Message): aws.model.DeleteMessageResult = deleteMessage(message)
89 | def deleteMessage(message: Message, requestCredentials: Option[AWSSessionCredentials] = None): aws.model.DeleteMessageResult = {
90 | val request = new aws.model.DeleteMessageRequest(message.queue.url, message.receiptHandle)
91 | requestCredentials.foreach(c => request.setRequestCredentials(c))
92 | deleteMessage(request)
93 | }
94 | def deleteMessages(messages: Seq[Message], requestCredentials: Option[AWSSessionCredentials] = None): aws.model.DeleteMessageBatchResult = {
95 | val batchId = s"${Thread.currentThread.getId}-${System.nanoTime}"
96 | deleteMessageBatch(
97 | messages.head.queue,
98 | messages.zipWithIndex.map { case (msg, idx) => new DeleteMessageBatchEntry(s"${batchId}-${idx}", msg.receiptHandle) },
99 | requestCredentials)
100 | }
101 | def deleteMessageBatch(queue: Queue, messages: Seq[DeleteMessageBatchEntry], requestCredentials: Option[AWSSessionCredentials] = None): aws.model.DeleteMessageBatchResult = {
102 | val request = new aws.model.DeleteMessageBatchRequest(
103 | queue.url,
104 | messages.map(_.asInstanceOf[aws.model.DeleteMessageBatchRequestEntry]).asJava)
105 | requestCredentials.foreach(c => request.setRequestCredentials(c))
106 | deleteMessageBatch(request)
107 | }
108 |
109 | }
110 |
111 | /**
112 | * SQSClient with specified queue.
113 | *
114 | * {{{
115 | * val sqs = SQS.at(Region.Tokyo)
116 | * sqs.withQueue(sqs.queue("queue-name").get) { s =>
117 | * s.sendMessage("only body!")
118 | * }
119 | * }}}
120 | *
121 | * @param sqs sqs
122 | * @param queue queue
123 | */
124 | class SQSClientWithQueue(sqs: SQS, queue: Queue) {
125 |
126 | def sendMessage(messageBody: String) = sqs.sendMessage(queue, messageBody)
127 | def sendMessages(messages: String*) = sqs.sendMessages(queue, messages)
128 | def sendMessageBatch(messages: MessageBatchEntry*) = sqs.sendMessageBatch(queue, messages)
129 |
130 | def receive() = receiveMessage()
131 | def receiveMessage() = sqs.receiveMessage(queue)
132 |
133 | def delete(message: Message) = sqs.delete(message)
134 | def deleteMessage(message: Message) = sqs.deleteMessage(message)
135 | def deleteMessages(messages: Message*) = sqs.deleteMessages(messages)
136 | def deleteMessageBatch(messages: DeleteMessageBatchEntry*) = sqs.deleteMessageBatch(queue, messages)
137 | def queueAttributes(attributeName: String) = sqs.queueAttributes(queue, attributeName)
138 | }
139 |
140 | /**
141 | * Default Implementation
142 | *
143 | * @param credentialsProvider credentialsProvider
144 | */
145 | class SQSClient(credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load())
146 | extends aws.AmazonSQSClient(credentialsProvider)
147 | with SQS
148 |
--------------------------------------------------------------------------------
/sqs/src/main/scala/awscala/sqs/package.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | package object sqs {
4 |
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/sqs/src/test/scala/awscala/SQSSpec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import awscala._, sqs._
4 |
5 | import org.slf4j._
6 | import org.scalatest._
7 | import org.scalatest.flatspec.AnyFlatSpec
8 | import org.scalatest.matchers.should.Matchers
9 |
10 | class SQSSpec extends AnyFlatSpec with Matchers {
11 |
12 | behavior of "SQS"
13 |
14 | val log = LoggerFactory.getLogger(this.getClass)
15 |
16 | it should "provide cool APIs" in {
17 | implicit val sqs = SQS.at(Region.Tokyo)
18 |
19 | // queues
20 | val queues = sqs.queues
21 | log.info(s"Queues : ${queues}")
22 |
23 | // create new queue
24 | val newQueueName = s"sample-queue-${System.currentTimeMillis}"
25 | val queue = sqs.createQueueAndReturnQueueName(newQueueName)
26 | val url = sqs.queueUrl(newQueueName)
27 | log.info(s"Created queue: ${queue}, url: ${url}")
28 |
29 | // get queue attributes before inserting any message
30 | val attribute = sqs.queueAttributes(queue, "ApproximateNumberOfMessages")
31 | log.info(s"Attribute for queue before inserting any message")
32 | attribute.keys.foreach { i =>
33 | log.info(s"Attribute Name = ${i}")
34 | log.info(s"Value = ${attribute}(${i})")
35 | }
36 |
37 | // send messages
38 | val sent = queue.add("some message!")
39 | log.info(s"Sent : ${sent}")
40 | val sendMessages = queue.add("first", "second", "third")
41 | log.info(s"Batch Sent : ${sendMessages}")
42 |
43 | // get queue attributes after inserting any message
44 | val attribute2 = sqs.queueAttributes(queue, "ApproximateNumberOfMessages")
45 | log.info(s"Attribute for queue after inserting any message")
46 | attribute2.keys.foreach { i =>
47 | log.info(s"Attribute Name = ${i}")
48 | log.info(s"Value = ${attribute2}(${i})")
49 | }
50 |
51 | // receive messages
52 | val receivedMessages = queue.messages() // or sqs.receiveMessage(queue)
53 | log.info(s"Received : ${receivedMessages}")
54 |
55 | // delete messages
56 | queue.removeAll(receivedMessages)
57 |
58 | // working with specified queue
59 | sqs.withQueue(queue) { s =>
60 | s.sendMessage("some message!")
61 | s.sendMessages("first", "second", "third")
62 | s.receiveMessage().foreach(msg => s.deleteMessage(msg))
63 | }
64 |
65 | // delete a queue
66 | queue.destroy() // or sqs.deleteQueue(queue)
67 | log.info(s"Deleted queue: ${queue}")
68 |
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/stepfunctions/src/main/scala/awscala/stepfunctions/Activity.scala:
--------------------------------------------------------------------------------
1 | package awscala.stepfunctions
2 |
3 | import awscala.stepfunctions.ArnFormat.ResourceArn
4 | import com.amazonaws.services.stepfunctions.model.DeleteActivityRequest
5 |
6 | case class Activity(arn: String) {
7 | val name: String = ArnFormat.parseArn(arn, ResourceArn)
8 |
9 | def delete()(implicit steps: StepFunctions): Unit =
10 | steps.deleteActivity(new DeleteActivityRequest().withActivityArn(arn))
11 | }
12 |
--------------------------------------------------------------------------------
/stepfunctions/src/main/scala/awscala/stepfunctions/ArnFormat.scala:
--------------------------------------------------------------------------------
1 | package awscala.stepfunctions
2 |
3 | object ArnFormat {
4 | sealed trait ArnFormat
5 | case object TypedResourceArn extends ArnFormat
6 | case object ResourceArn extends ArnFormat
7 |
8 | def parseArn(arn: String, format: ArnFormat): String = {
9 | val limit = format match {
10 | case TypedResourceArn => 8
11 | case ResourceArn => 7
12 | }
13 | arn.split(":", limit).last
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/stepfunctions/src/main/scala/awscala/stepfunctions/Execution.scala:
--------------------------------------------------------------------------------
1 | package awscala.stepfunctions
2 |
3 | import java.util
4 |
5 | import awscala.stepfunctions.ArnFormat.TypedResourceArn
6 | import awscala.stepfunctions.ExecutionEventDetails.{ EventFailed, StateFailed, StateStarted, StateSucceeded }
7 | import awscala.stepfunctions.ExecutionStatus.{ ExecutionStatus, Failed, NotStarted, Running, Succeeded }
8 | import awscala.{ DateTime, Sequencer }
9 | import com.amazonaws.services.stepfunctions.model.{ DescribeExecutionRequest, GetExecutionHistoryRequest, GetExecutionHistoryResult, HistoryEvent }
10 |
11 | import scala.annotation.tailrec
12 |
13 | case class Execution(arn: String, startTime: DateTime) {
14 | val name: String = ArnFormat.parseArn(arn, TypedResourceArn)
15 |
16 | def details()(implicit steps: StepFunctions): ExecutionDetails = {
17 | val details = steps.describeExecution(new DescribeExecutionRequest().withExecutionArn(arn))
18 | ExecutionDetails(
19 | arn,
20 | startTime,
21 | Option(details.getStopDate).map(new DateTime(_)),
22 | ExecutionStatus.fromString(details.getStatus),
23 | details.getInput,
24 | Option(details.getOutput))
25 | }
26 |
27 | def stepStatus(name: String)(implicit steps: StepFunctions): ExecutionStatus = {
28 | val hist = history()
29 | def getById(id: Long): Option[ExecutionEvent] = hist.find(_.id == id)
30 | @tailrec
31 | def startedByEvent(id: Long): Boolean =
32 | getById(id) match {
33 | case Some(ExecutionEvent(_, _, _, StateStarted(`name`))) => true
34 | case Some(ExecutionEvent(_, _, _, StateStarted(_))) => false
35 | case None => false
36 | case Some(ExecutionEvent(_, prev, _, _)) => startedByEvent(prev)
37 | }
38 |
39 | val started = hist.exists {
40 | case ExecutionEvent(_, _, _, StateStarted(`name`)) => true
41 | case _ => false
42 | }
43 | val succeeded = hist.exists {
44 | case ExecutionEvent(_, _, _, StateSucceeded(`name`, _)) => true
45 | case _ => false
46 | }
47 | val failedId = hist.collect {
48 | case ExecutionEvent(id, _, _, StateFailed(`name`, _, _)) => Some(id)
49 | case ExecutionEvent(id, prev, _, EventFailed(_, _, _)) if startedByEvent(prev) => id
50 | }.lastOption
51 |
52 | if (!started) {
53 | NotStarted
54 | } else if (succeeded) {
55 | Succeeded
56 | } else {
57 | failedId
58 | .map { id =>
59 | val nextEvent = hist.find {
60 | case ExecutionEvent(_, `id`, _, _) => true
61 | case _ => false
62 | }
63 | nextEvent match {
64 | case Some(ExecutionEvent(_, _, _, StateStarted(_))) => Running // We retried the failure
65 | case _ => Failed
66 | }
67 | }
68 | .getOrElse(Running) // This could still be retried
69 | }
70 | }
71 |
72 | def history()(implicit steps: StepFunctions): Seq[ExecutionEvent] = {
73 | object HistorySequencer extends Sequencer[HistoryEvent, GetExecutionHistoryResult, String] {
74 | private val base = new GetExecutionHistoryRequest().withExecutionArn(arn)
75 | def getInitial: GetExecutionHistoryResult = steps.getExecutionHistory(base)
76 |
77 | def getMarker(r: GetExecutionHistoryResult): String = r.getNextToken
78 |
79 | def getFromMarker(marker: String): GetExecutionHistoryResult =
80 | steps.getExecutionHistory(base.withNextToken(marker))
81 |
82 | def getList(r: GetExecutionHistoryResult): util.List[HistoryEvent] = r.getEvents
83 | }
84 | HistorySequencer.sequence.flatMap { rawEvent =>
85 | ExecutionEventDetails.fromEvent(rawEvent).map { event =>
86 | ExecutionEvent(rawEvent.getId, rawEvent.getPreviousEventId, new DateTime(rawEvent.getTimestamp), event)
87 | }
88 | }
89 | }
90 |
91 | def status()(implicit steps: StepFunctions): ExecutionStatus = details().status
92 | def endTime()(implicit steps: StepFunctions): Option[DateTime] = details().endTime
93 | def input()(implicit steps: StepFunctions): String = details().input
94 | def output()(implicit steps: StepFunctions): Option[String] = details().output
95 | }
96 |
--------------------------------------------------------------------------------
/stepfunctions/src/main/scala/awscala/stepfunctions/ExecutionDetails.scala:
--------------------------------------------------------------------------------
1 | package awscala.stepfunctions
2 |
3 | import awscala.DateTime
4 | import awscala.stepfunctions.ExecutionStatus.ExecutionStatus
5 |
6 | case class ExecutionDetails(
7 | arn: String,
8 | startTime: DateTime,
9 | endTime: Option[DateTime],
10 | status: ExecutionStatus,
11 | input: String,
12 | output: Option[String])
13 |
--------------------------------------------------------------------------------
/stepfunctions/src/main/scala/awscala/stepfunctions/ExecutionEvent.scala:
--------------------------------------------------------------------------------
1 | package awscala.stepfunctions
2 |
3 | import awscala.DateTime
4 | import awscala.stepfunctions.ExecutionEventDetails.ExecutionEventDetails
5 |
6 | case class ExecutionEvent(
7 | id: Long,
8 | previousId: Long,
9 | timestamp: DateTime,
10 | details: ExecutionEventDetails)
11 |
--------------------------------------------------------------------------------
/stepfunctions/src/main/scala/awscala/stepfunctions/ExecutionEventDetails.scala:
--------------------------------------------------------------------------------
1 | package awscala.stepfunctions
2 |
3 | import awscala.stepfunctions.ExecutionStatus.{ Aborted, ExecutionStatus, Failed, TimedOut }
4 | import com.amazonaws.services.stepfunctions.model.HistoryEvent
5 |
6 | object ExecutionEventDetails {
7 | sealed trait ExecutionEventDetails
8 | case class EventSucceeded(output: String) extends ExecutionEventDetails
9 | case class EventFailed(error: Option[String], cause: Option[String], failureType: ExecutionStatus) extends ExecutionEventDetails
10 | case class EventScheduled(input: String, resource: String, timeout: Option[Long], heartbeat: Option[Long])
11 | extends ExecutionEventDetails
12 | case class EventStarted(workerName: Option[String]) extends ExecutionEventDetails
13 | case class StateStarted(name: String) extends ExecutionEventDetails
14 | case class StateFailed(name: String, output: String, status: ExecutionStatus) extends ExecutionEventDetails
15 | case class StateSucceeded(name: String, output: String) extends ExecutionEventDetails
16 |
17 | def fromEvent(e: HistoryEvent): Option[ExecutionEventDetails] = Option {
18 | e.getType match {
19 | case "ActivityFailed" =>
20 | EventFailed(
21 | Option(e.getActivityFailedEventDetails.getError),
22 | Option(e.getActivityFailedEventDetails.getCause),
23 | Failed)
24 | case "ActivityScheduleFailed" =>
25 | EventFailed(
26 | Option(e.getActivityScheduleFailedEventDetails.getError),
27 | Option(e.getActivityScheduleFailedEventDetails.getCause),
28 | Failed)
29 | case "ActivityScheduled" =>
30 | EventScheduled(
31 | e.getActivityScheduledEventDetails.getInput,
32 | e.getActivityScheduledEventDetails.getResource,
33 | zeroAsNone(e.getActivityScheduledEventDetails.getTimeoutInSeconds),
34 | zeroAsNone(e.getActivityScheduledEventDetails.getHeartbeatInSeconds))
35 | case "ActivityStarted" => EventStarted(Option(e.getActivityStartedEventDetails.getWorkerName))
36 | case "ActivitySucceeded" => EventSucceeded(e.getActivitySucceededEventDetails.getOutput)
37 | case "ActivityTimedOut" =>
38 | EventFailed(
39 | Option(e.getActivityTimedOutEventDetails.getError),
40 | Option(e.getActivityTimedOutEventDetails.getCause),
41 | TimedOut)
42 |
43 | case "ChoiceStateEntered" => StateStarted(e.getStateEnteredEventDetails.getName)
44 | case "ChoiceStateExited" =>
45 | StateSucceeded(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput)
46 |
47 | case "ExecutionFailed" => null
48 | case "ExecutionStarted" => null
49 | case "ExecutionSucceeded" => null
50 | case "ExecutionAborted" => null
51 | case "ExecutionTimedOut" => null
52 |
53 | case "FailStateEntered" => StateStarted(e.getStateEnteredEventDetails.getName)
54 |
55 | case "LambdaFunctionFailed" =>
56 | EventFailed(
57 | Option(e.getLambdaFunctionFailedEventDetails.getError),
58 | Option(e.getLambdaFunctionFailedEventDetails.getCause),
59 | Failed)
60 | case "LambdaFunctionScheduleFailed" =>
61 | EventFailed(
62 | Option(e.getLambdaFunctionScheduleFailedEventDetails.getError),
63 | Option(e.getLambdaFunctionScheduleFailedEventDetails.getCause),
64 | Failed)
65 | case "LambdaFunctionScheduled" =>
66 | EventScheduled(
67 | e.getLambdaFunctionScheduledEventDetails.getInput,
68 | e.getLambdaFunctionScheduledEventDetails.getResource,
69 | zeroAsNone(e.getLambdaFunctionScheduledEventDetails.getTimeoutInSeconds),
70 | None)
71 | case "LambdaFunctionStartFailed" =>
72 | EventFailed(
73 | Option(e.getLambdaFunctionStartFailedEventDetails.getError),
74 | Option(e.getLambdaFunctionStartFailedEventDetails.getCause),
75 | Failed)
76 | case "LambdaFunctionStarted" => EventStarted(None)
77 | case "LambdaFunctionSucceeded" =>
78 | EventSucceeded(e.getLambdaFunctionSucceededEventDetails.getOutput)
79 | case "LambdaFunctionTimedOut" =>
80 | EventFailed(
81 | Option(e.getLambdaFunctionTimedOutEventDetails.getError),
82 | Option(e.getLambdaFunctionTimedOutEventDetails.getCause),
83 | TimedOut)
84 |
85 | case "SucceedStateEntered" => StateStarted(e.getStateEnteredEventDetails.getName)
86 | case "SucceedStateExited" =>
87 | StateSucceeded(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput)
88 |
89 | case "TaskStateAborted" =>
90 | StateFailed(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput, Aborted)
91 | case "TaskStateEntered" => StateStarted(e.getStateEnteredEventDetails.getName)
92 | case "TaskStateExited" =>
93 | StateSucceeded(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput)
94 |
95 | case "PassStateEntered" => StateStarted(e.getStateEnteredEventDetails.getName)
96 | case "PassStateExited" =>
97 | StateSucceeded(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput)
98 |
99 | case "ParallelStateAborted" =>
100 | StateFailed(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput, Aborted)
101 | case "ParallelStateEntered" => StateStarted(e.getStateEnteredEventDetails.getName)
102 | case "ParallelStateExited" =>
103 | StateSucceeded(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput)
104 | case "ParallelStateFailed" =>
105 | StateFailed(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput, Failed)
106 | case "ParallelStateStarted" => null
107 | case "ParallelStateSucceeded" => null
108 |
109 | case "WaitStateAborted" =>
110 | StateFailed(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput, Aborted)
111 | case "WaitStateEntered" => StateStarted(e.getStateEnteredEventDetails.getName)
112 | case "WaitStateExited" =>
113 | StateSucceeded(e.getStateExitedEventDetails.getName, e.getStateExitedEventDetails.getOutput)
114 | }
115 | }
116 |
117 | def zeroAsNone(x: Long): Option[Long] = if (x == 0) None else Some(x)
118 | }
119 |
--------------------------------------------------------------------------------
/stepfunctions/src/main/scala/awscala/stepfunctions/ExecutionStatus.scala:
--------------------------------------------------------------------------------
1 | package awscala.stepfunctions
2 |
3 | object ExecutionStatus {
4 | sealed trait ExecutionStatus
5 | case object NotStarted extends ExecutionStatus
6 | case object Running extends ExecutionStatus
7 | case object Succeeded extends ExecutionStatus
8 | case object Failed extends ExecutionStatus
9 | case object TimedOut extends ExecutionStatus
10 | case object Aborted extends ExecutionStatus
11 |
12 | def fromString(status: String): ExecutionStatus = status.toLowerCase match {
13 | case "running" => Running
14 | case "succeeded" => Succeeded
15 | case "failed" => Failed
16 | case "timed_out" => TimedOut
17 | case "aborted" => TimedOut
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/stepfunctions/src/main/scala/awscala/stepfunctions/StateMachine.scala:
--------------------------------------------------------------------------------
1 | package awscala.stepfunctions
2 |
3 | import java.util
4 |
5 | import awscala.{ DateTime, Sequencer }
6 | import awscala.stepfunctions.ArnFormat.ResourceArn
7 | import com.amazonaws.services.stepfunctions.model._
8 |
9 | import scala.jdk.CollectionConverters._
10 |
11 | case class StateMachine(arn: String) {
12 | val name: String = ArnFormat.parseArn(arn, ResourceArn)
13 |
14 | def startExecution(input: String, name: String = null)(implicit steps: StepFunctions): Execution = {
15 | val exec =
16 | steps.startExecution {
17 | new StartExecutionRequest()
18 | .withStateMachineArn(arn)
19 | .withInput(input)
20 | .withName(name)
21 | }
22 | Execution(exec.getExecutionArn, new DateTime(exec.getStartDate))
23 | }
24 |
25 | def execution(name: String)(implicit steps: StepFunctions): Option[Execution] = executions().find(_.name == name)
26 |
27 | def executions()(implicit steps: StepFunctions): Seq[Execution] = {
28 | object ExecutionsSequencer extends Sequencer[ExecutionListItem, ListExecutionsResult, String] {
29 | val base = new ListExecutionsRequest().withStateMachineArn(arn)
30 | def getInitial: ListExecutionsResult = steps.listExecutions(base)
31 |
32 | def getMarker(r: ListExecutionsResult): String = r.getNextToken
33 |
34 | def getFromMarker(marker: String): ListExecutionsResult = steps.listExecutions(base.withNextToken(marker))
35 |
36 | def getList(r: ListExecutionsResult): util.List[ExecutionListItem] = r.getExecutions
37 | }
38 | ExecutionsSequencer.sequence.map(e => Execution(e.getExecutionArn, new DateTime(e.getStartDate)))
39 | }
40 |
41 | def definition()(implicit steps: StepFunctions): String =
42 | steps.describeStateMachine(new DescribeStateMachineRequest().withStateMachineArn(arn)).getDefinition
43 |
44 | def update(definition: Option[String] = None, roleArn: Option[String] = None)(implicit steps: StepFunctions): Unit =
45 | if (definition.isDefined || roleArn.isDefined) {
46 | steps.updateStateMachine(
47 | new UpdateStateMachineRequest()
48 | .withStateMachineArn(arn)
49 | .withDefinition(definition.orNull)
50 | .withRoleArn(roleArn.orNull))
51 | }
52 |
53 | def delete()(implicit steps: StepFunctions): Unit =
54 | steps.deleteStateMachine(new DeleteStateMachineRequest().withStateMachineArn(arn))
55 | }
56 |
--------------------------------------------------------------------------------
/stepfunctions/src/main/scala/awscala/stepfunctions/StepFunctions.scala:
--------------------------------------------------------------------------------
1 | package awscala.stepfunctions
2 |
3 | import java.util
4 | import java.util.concurrent.TimeUnit
5 |
6 | import awscala._
7 | import com.amazonaws.ClientConfiguration
8 | import com.amazonaws.auth.AWSCredentialsProvider
9 | import com.amazonaws.services.stepfunctions.model._
10 | import com.amazonaws.services.{ stepfunctions => aws }
11 |
12 | import scala.jdk.CollectionConverters._
13 | import scala.concurrent.{ blocking, ExecutionContext, Future }
14 | import scala.util.{ Failure, Success, Try }
15 |
16 | object StepFunctions {
17 | private val DEFAULT_SOCKET_TIMEOUT = TimeUnit.SECONDS.toMillis(70).toInt
18 |
19 | def apply(credentials: Credentials)(implicit region: Region): StepFunctions =
20 | apply(BasicCredentialsProvider(credentials.getAWSAccessKeyId, credentials.getAWSSecretKey))(region)
21 |
22 | def apply(accessKeyId: String, secretAccessKey: String)(implicit region: Region): StepFunctions =
23 | apply(BasicCredentialsProvider(accessKeyId, secretAccessKey))(region)
24 |
25 | def apply(credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load())(
26 | implicit
27 | region: Region = Region.default()): StepFunctions =
28 | new StepFunctionsClient(new ClientConfiguration().withSocketTimeout(DEFAULT_SOCKET_TIMEOUT), credentialsProvider)
29 | .at(region)
30 |
31 | def apply(clientConfiguration: ClientConfiguration, credentials: Credentials)(
32 | implicit
33 | region: Region): StepFunctions =
34 | apply(clientConfiguration, BasicCredentialsProvider(credentials.getAWSAccessKeyId, credentials.getAWSSecretKey))(
35 | region)
36 |
37 | def apply(clientConfiguration: ClientConfiguration, accessKeyId: String, secretAccessKey: String)(
38 | implicit
39 | region: Region): StepFunctions =
40 | apply(clientConfiguration, BasicCredentialsProvider(accessKeyId, secretAccessKey))(region)
41 |
42 | def apply(clientConfiguration: ClientConfiguration, credentialsProvider: AWSCredentialsProvider)(
43 | implicit
44 | region: Region): StepFunctions =
45 | new StepFunctionsClient(
46 | clientConfiguration.withSocketTimeout(DEFAULT_SOCKET_TIMEOUT),
47 | credentialsProvider).at(region)
48 |
49 | def at(region: Region): StepFunctions = apply()(region)
50 | }
51 |
52 | trait StepFunctions extends aws.AWSStepFunctions {
53 | def at(region: Region): StepFunctions = {
54 | this.setRegion(region)
55 | this
56 | }
57 |
58 | def createStateMachine(name: String, definition: String, roleArn: String): StateMachine = StateMachine {
59 | createStateMachine(
60 | new CreateStateMachineRequest()
61 | .withName(name)
62 | .withDefinition(definition)
63 | .withRoleArn(roleArn)).getStateMachineArn
64 | }
65 |
66 | def stateMachine(name: String): Option[StateMachine] = stateMachines.find(_.name == name)
67 |
68 | def stateMachines: Seq[StateMachine] = {
69 | object StateMachineSequencer extends Sequencer[StateMachineListItem, ListStateMachinesResult, String] {
70 | val base = new ListStateMachinesRequest()
71 | def getInitial: ListStateMachinesResult = listStateMachines(base)
72 |
73 | def getMarker(r: ListStateMachinesResult): String = r.getNextToken
74 |
75 | def getFromMarker(marker: String): ListStateMachinesResult = listStateMachines(base.withNextToken(marker))
76 |
77 | def getList(r: ListStateMachinesResult): util.List[StateMachineListItem] = r.getStateMachines
78 | }
79 | StateMachineSequencer.sequence.map(sm => StateMachine(sm.getStateMachineArn))
80 | }
81 |
82 | def createActivity(name: String): Activity = Activity {
83 | createActivity(new CreateActivityRequest().withName(name)).getActivityArn
84 | }
85 |
86 | def activity(name: String): Option[Activity] = activities.find(_.name == name)
87 |
88 | def activities: Seq[Activity] = {
89 | object ActivitiesSequencer extends Sequencer[ActivityListItem, ListActivitiesResult, String] {
90 | private val base = new ListActivitiesRequest()
91 | def getInitial: ListActivitiesResult = listActivities(base)
92 |
93 | def getMarker(r: ListActivitiesResult): String = r.getNextToken
94 |
95 | def getFromMarker(marker: String): ListActivitiesResult = listActivities(base.withNextToken(marker))
96 |
97 | def getList(r: ListActivitiesResult): util.List[ActivityListItem] = r.getActivities
98 | }
99 | ActivitiesSequencer.sequence.map(a => Activity(a.getActivityArn))
100 | }
101 |
102 | def runActivity(name: String, workerName: String = null)(fn: String => String)(
103 | implicit
104 | ec: ExecutionContext): Future[Option[String]] =
105 | Future {
106 | activity(name).map(_.arn).map { arn =>
107 | blocking {
108 | getActivityTask(new GetActivityTaskRequest().withActivityArn(arn).withWorkerName(workerName))
109 | }
110 | }
111 | }.map { s =>
112 | s.flatMap(ac => Option(ac.getTaskToken).map((_, ac.getInput)))
113 | .map {
114 | case (token, input) =>
115 | val result = Try(fn(input))
116 | result match {
117 | case Failure(err) =>
118 | sendTaskFailure(
119 | new SendTaskFailureRequest()
120 | .withTaskToken(token)
121 | .withCause(Option(err.getCause).map(_.getMessage).orNull)
122 | .withError(err.getMessage))
123 | throw err
124 | case Success(output) =>
125 | sendTaskSuccess(new SendTaskSuccessRequest().withTaskToken(token).withOutput(output))
126 | output
127 | }
128 | }
129 | }
130 | }
131 |
132 | class StepFunctionsClient(
133 | clientConfiguration: ClientConfiguration,
134 | credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load())
135 | extends aws.AWSStepFunctionsClient(credentialsProvider, clientConfiguration)
136 | with StepFunctions
137 |
--------------------------------------------------------------------------------
/stepfunctions/src/test/scala/awscala/StepFunctionsSpec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import java.util.concurrent.TimeUnit
4 |
5 | import awscala.iam.IAM
6 | import awscala.stepfunctions.ExecutionStatus.{ Failed, NotStarted, Succeeded }
7 | import awscala.stepfunctions.StepFunctions
8 | import com.amazonaws.auth.policy.Principal
9 | import org.scalatest._
10 | import org.slf4j.LoggerFactory
11 |
12 | import scala.concurrent.duration.Duration
13 | import scala.concurrent.{ Await, Future }
14 | import scala.util.Try
15 | import org.scalatest.flatspec.AnyFlatSpec
16 | import org.scalatest.matchers.should.Matchers
17 |
18 | class StepFunctionsSpec extends AnyFlatSpec with Matchers {
19 |
20 | behavior of "StepFunctions"
21 |
22 | it should "provide cool APIs" in {
23 | implicit val steps = StepFunctions.at(Region.US_WEST_2)
24 | implicit val iam = IAM()
25 | val role = iam
26 | .createRole(
27 | "awscala-test-stepfunctions",
28 | "/service-role/",
29 | Policy(
30 | Seq(
31 | Statement(
32 | Effect.Allow,
33 | Seq(Action("sts:AssumeRole")),
34 | Seq.empty,
35 | principals = Seq(new Principal("Service", "states.us-west-2.amazonaws.com"))))))
36 | val failActivity = steps.createActivity("fail")
37 | val succeedActivity = steps.createActivity("succeed")
38 | val parallelActivity = steps.createActivity("parallel")
39 | val machineDefinition =
40 | s"""
41 | |{
42 | | "StartAt": "Test Type",
43 | | "States": {
44 | | "Test Type": {
45 | | "Type": "Choice",
46 | | "Choices": [
47 | | {
48 | | "Variable": "$$.type",
49 | | "StringEquals": "FAIL",
50 | | "Next": "Fail Step"
51 | | },
52 | | {
53 | | "Variable": "$$.type",
54 | | "StringEquals": "PARALLEL",
55 | | "Next": "Parallel Step"
56 | | }
57 | | ],
58 | | "Default": "Succeed Step"
59 | | },
60 | | "Fail Step": {
61 | | "Type": "Task",
62 | | "TimeoutSeconds": 5,
63 | | "Resource": "${failActivity.arn}",
64 | | "End": true
65 | | },
66 | | "Succeed Step": {
67 | | "Type": "Task",
68 | | "TimeoutSeconds": 5,
69 | | "Resource": "${succeedActivity.arn}",
70 | | "Next": "Succeed Step Again"
71 | | },
72 | | "Succeed Step Again": {
73 | | "Type": "Task",
74 | | "TimeoutSeconds": 5,
75 | | "Resource": "${succeedActivity.arn}",
76 | | "End": true
77 | | },
78 | | "Parallel Step": {
79 | | "Type": "Parallel",
80 | | "Branches": [
81 | | {
82 | | "StartAt": "ParPass",
83 | | "States": {
84 | | "ParPass": {
85 | | "Type": "Pass",
86 | | "End": true
87 | | }
88 | | }
89 | | },
90 | | {
91 | | "StartAt": "Inner Parallel",
92 | | "States": {
93 | | "Inner Parallel": {
94 | | "Type": "Task",
95 | | "Resource": "${parallelActivity.arn}",
96 | | "End": true
97 | | }
98 | | }
99 | | }
100 | | ],
101 | | "End": true
102 | | }
103 | | }
104 | |}
105 | """.stripMargin
106 | val machine = steps.createStateMachine("test", machineDefinition, role.arn)
107 |
108 | implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
109 | try {
110 | val input = """{"type":"SUCCEED"}"""
111 | val exec = machine.startExecution(input)
112 | Await.ready(
113 | Future.sequence {
114 | Seq(steps.runActivity(succeedActivity.name)(identity), steps.runActivity(succeedActivity.name)(identity))
115 | },
116 | Duration(30, TimeUnit.SECONDS))
117 | assert(exec.stepStatus("Succeed Step") === Succeeded)
118 | assert(exec.stepStatus("Succeed Step Again") === Succeeded)
119 | assert(exec.stepStatus("Fail Step") === NotStarted)
120 | val details = exec.details()
121 | assert(details.status === Succeeded)
122 | assert(details.output === Some(details.input))
123 | assert(details.input === input)
124 |
125 | val exec2 = machine.startExecution("""{"type":"FAIL"}""")
126 | intercept[IllegalArgumentException] {
127 | Await.result(
128 | steps.runActivity(failActivity.name)(_ => throw new IllegalArgumentException()),
129 | Duration(30, TimeUnit.SECONDS))
130 | }
131 | assert(exec2.stepStatus("Fail Step") === Failed)
132 | assert(exec2.stepStatus("Succeed Step") === NotStarted)
133 |
134 | val exec3 = machine.startExecution("""{"type":"PARALLEL"}""")
135 | Await.result(steps.runActivity(parallelActivity.name)(identity), Duration(30, TimeUnit.SECONDS))
136 | assert(exec3.stepStatus("Inner Parallel") === Succeeded)
137 | assert(exec3.stepStatus("Fail Step") === NotStarted)
138 | assert(exec3.stepStatus("Succeed Step") === NotStarted)
139 |
140 | assert(steps.stateMachines.contains(machine))
141 | assert(steps.stateMachine(machine.name) === Some(machine))
142 | assert(machine.executions() === Seq(exec3, exec2, exec))
143 | assert(machine.execution(exec.name) === Some(exec))
144 | assert(machine.definition() === machineDefinition)
145 | } finally {
146 | machine.delete()
147 | role.destroy()
148 | failActivity.delete()
149 | succeedActivity.delete()
150 | parallelActivity.delete()
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/sts/src/main/scala/awscala/sts/FederatedUser.scala:
--------------------------------------------------------------------------------
1 | package awscala.sts
2 |
3 | import com.amazonaws.services.{ securitytoken => aws }
4 |
5 | object FederatedUser {
6 | def apply(u: aws.model.FederatedUser): FederatedUser = new FederatedUser(
7 | arn = u.getArn,
8 | userId = u.getFederatedUserId)
9 | }
10 | case class FederatedUser(arn: String, userId: String) extends aws.model.FederatedUser {
11 | setArn(arn)
12 | setFederatedUserId(userId)
13 | }
14 |
15 |
--------------------------------------------------------------------------------
/sts/src/main/scala/awscala/sts/FederationToken.scala:
--------------------------------------------------------------------------------
1 | package awscala.sts
2 |
3 | case class FederationToken(user: FederatedUser, credentials: TemporaryCredentials)
4 |
5 |
--------------------------------------------------------------------------------
/sts/src/main/scala/awscala/sts/STS.scala:
--------------------------------------------------------------------------------
1 | package awscala.sts
2 |
3 | import awscala._
4 | import com.amazonaws.auth.AWSCredentialsProvider
5 | import com.amazonaws.services.{ securitytoken => aws }
6 | import com.amazonaws.util.json.Jackson
7 | import java.net._
8 |
9 | object STS {
10 | def apply(credentials: Credentials)(implicit region: Region): STS = new STSClient(BasicCredentialsProvider(credentials.getAWSAccessKeyId, credentials.getAWSSecretKey))
11 | def apply(credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load()): STS = new STSClient(credentialsProvider)
12 | def apply(accessKeyId: String, secretAccessKey: String): STS = {
13 | new STSClient(BasicCredentialsProvider(accessKeyId, secretAccessKey))
14 | }
15 | }
16 |
17 | /**
18 | * Amazon Security Token Service Java client wrapper
19 | * @see [[http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/]]
20 | */
21 | trait STS extends aws.AWSSecurityTokenService {
22 |
23 | def sessionToken: SessionToken = SessionToken(TemporaryCredentials(getSessionToken().getCredentials))
24 |
25 | def sessionToken(serialNumber: String, tokenCode: String, durationSeconds: Int): SessionToken = {
26 | SessionToken(TemporaryCredentials(getSessionToken(
27 | new aws.model.GetSessionTokenRequest()
28 | .withSerialNumber(serialNumber)
29 | .withTokenCode(tokenCode)
30 | .withDurationSeconds(durationSeconds)).getCredentials))
31 | }
32 |
33 | def federationToken(name: String, policy: Policy, durationSeconds: Int): FederationToken = {
34 | val result = getFederationToken(new aws.model.GetFederationTokenRequest()
35 | .withName(name)
36 | .withPolicy(policy.toJSON)
37 | .withDurationSeconds(durationSeconds))
38 |
39 | FederationToken(
40 | user = FederatedUser(result.getFederatedUser),
41 | credentials = TemporaryCredentials(result.getCredentials))
42 | }
43 |
44 | def decodeAuthorizationMessage(message: String): String = {
45 | decodeAuthorizationMessage(new aws.model.DecodeAuthorizationMessageRequest().withEncodedMessage(message)).getDecodedMessage
46 | }
47 |
48 | private[this] val SIGNIN_URL = "https://signin.aws.amazon.com/federation"
49 |
50 | def signinToken(credentials: TemporaryCredentials): String = {
51 | val sessionJsonValue = s"""{"sessionId":"${credentials.accessKeyId}","sessionKey":"${credentials.secretAccessKey}","sessionToken":"${credentials.sessionToken}"}\n"""
52 | val url = SIGNIN_URL + "?Action=getSigninToken&SessionType=json&Session=" + java.net.URLEncoder.encode(sessionJsonValue, "UTF-8")
53 | val source = scala.io.Source.fromURL(new java.net.URL(url))
54 | val response = source.getLines().mkString("\n")
55 | source.close()
56 | Jackson.jsonNodeOf(response).get("SigninToken").asText()
57 | }
58 |
59 | def loginUrl(credentials: TemporaryCredentials, consoleUrl: String = "https://console.aws.amazon.com/iam", issuerUrl: String = ""): String = {
60 | val token = URLEncoder.encode(signinToken(credentials), "UTF-8")
61 | val issuer = URLEncoder.encode(issuerUrl, "UTF-8")
62 | val destination = URLEncoder.encode(consoleUrl, "UTF-8")
63 | s"$SIGNIN_URL?Action=login&SigninToken=$token&Issuer=$issuer&Destination=$destination"
64 | }
65 |
66 | def assumeRole(id: String, arn: String, sessionKey: String): TemporaryCredentials = {
67 | val assumeRoleReq = new aws.model.AssumeRoleRequest()
68 | assumeRoleReq.setExternalId(id)
69 | assumeRoleReq.setRoleArn(arn)
70 | assumeRoleReq.setRoleSessionName(sessionKey)
71 | val response = assumeRole(assumeRoleReq)
72 | TemporaryCredentials(response.getCredentials)
73 | }
74 | }
75 |
76 | /**
77 | * Default Implementation
78 | *
79 | * @param credentialsProvider credentialsProvider
80 | */
81 | class STSClient(credentialsProvider: AWSCredentialsProvider = CredentialsLoader.load())
82 | extends aws.AWSSecurityTokenServiceClient(credentialsProvider)
83 | with STS
84 |
--------------------------------------------------------------------------------
/sts/src/main/scala/awscala/sts/SessionToken.scala:
--------------------------------------------------------------------------------
1 | package awscala.sts
2 |
3 | case class SessionToken(credentials: TemporaryCredentials)
4 |
5 |
--------------------------------------------------------------------------------
/sts/src/main/scala/awscala/sts/TemporaryCredentials.scala:
--------------------------------------------------------------------------------
1 | package awscala.sts
2 |
3 | import awscala._
4 | import com.amazonaws.services.{ securitytoken => aws }
5 | import com.amazonaws.auth.{ AWSSessionCredentials, BasicSessionCredentials }
6 |
7 | object TemporaryCredentials {
8 | def apply(c: aws.model.Credentials): TemporaryCredentials = new TemporaryCredentials(
9 | accessKeyId = c.getAccessKeyId,
10 | secretAccessKey = c.getSecretAccessKey,
11 | sessionToken = c.getSessionToken,
12 | expiration = new DateTime(c.getExpiration))
13 | }
14 |
15 | case class TemporaryCredentials(
16 | accessKeyId: String,
17 | secretAccessKey: String,
18 | sessionToken: String,
19 | expiration: DateTime) extends aws.model.Credentials {
20 |
21 | setAccessKeyId(accessKeyId)
22 | setExpiration(expiration.toDate)
23 | setSecretAccessKey(secretAccessKey)
24 | setSessionToken(sessionToken)
25 |
26 | def toSessionCredentials: AWSSessionCredentials = {
27 | new BasicSessionCredentials(accessKeyId, secretAccessKey, sessionToken)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/sts/src/test/scala/awscala/STSSpec.scala:
--------------------------------------------------------------------------------
1 | package awscala
2 |
3 | import awscala._, sts._
4 |
5 | import org.slf4j._
6 | import org.scalatest._
7 | import org.scalatest.flatspec.AnyFlatSpec
8 | import org.scalatest.matchers.should.Matchers
9 |
10 | class STSSpec extends AnyFlatSpec with Matchers {
11 |
12 | behavior of "STS"
13 |
14 | val log = LoggerFactory.getLogger(this.getClass)
15 |
16 | it should "provide cool APIs" in {
17 | implicit val sts = STS()
18 |
19 | val federation: FederationToken = sts.federationToken(
20 | name = "anonymous-user",
21 | policy = Policy(Seq(Statement(Effect.Allow, Seq(Action("s3:*")), Seq(Resource("*"))))),
22 | durationSeconds = 1200)
23 |
24 | val signinToken: String = sts.signinToken(federation.credentials)
25 | log.info(s"SigninToken: ${signinToken}")
26 |
27 | val loginUrl: String = sts.loginUrl(
28 | credentials = federation.credentials,
29 | consoleUrl = "https://console.aws.amazon.com/iam",
30 | issuerUrl = "http://example.com/internal/auth")
31 | log.info(s"LoginUrl: ${loginUrl}")
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------