├── .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 | --------------------------------------------------------------------------------