├── .clj-kondo ├── babashka │ └── fs │ │ └── config.edn ├── config.edn ├── http-kit │ └── http-kit │ │ ├── config.edn │ │ └── httpkit │ │ └── with_channel.clj └── rewrite-clj │ └── rewrite-clj │ └── config.edn ├── .dir-locals.el ├── .github └── pull_request_template.md ├── .gitignore ├── CHANGES.markdown ├── LICENSE ├── README.markdown ├── VERSION_PREFIX ├── bb.edn ├── bin ├── dc ├── lint ├── revision ├── tag └── test ├── deps.edn ├── dev ├── .dir-locals.el └── user.clj ├── docs └── porting-decisions.markdown ├── etc └── docker-compose.yaml ├── resources └── com_grzm_awyeah_http.edn ├── src └── com │ └── grzm │ └── awyeah │ ├── client.clj │ ├── client │ ├── api.clj │ ├── api │ │ └── async.clj │ ├── impl.clj │ ├── protocol.clj │ ├── shared.clj │ ├── test_double.clj │ └── validation.clj │ ├── config.clj │ ├── credentials.cljc │ ├── dynaload.clj │ ├── ec2_metadata_utils.clj │ ├── endpoint.clj │ ├── http.clj │ ├── http │ └── awyeah.clj │ ├── http_client.clj │ ├── http_client │ ├── client.clj │ └── specs.clj │ ├── interceptors.clj │ ├── json.clj │ ├── protocols.clj │ ├── protocols │ ├── ec2.clj │ ├── json.clj │ ├── query.clj │ ├── rest.clj │ ├── rest_json.clj │ └── rest_xml.clj │ ├── region.clj │ ├── retry.clj │ ├── service.clj │ ├── shape.clj │ ├── signers.clj │ └── util.clj └── test ├── .dir-locals.el ├── resources ├── .aws │ ├── config │ └── credentials ├── aws-sig-v4-test-suite │ ├── get-header-key-duplicate │ │ ├── get-header-key-duplicate.authz │ │ ├── get-header-key-duplicate.creq │ │ ├── get-header-key-duplicate.req │ │ ├── get-header-key-duplicate.sreq │ │ └── get-header-key-duplicate.sts │ ├── get-header-value-multiline │ │ ├── get-header-value-multiline.authz │ │ ├── get-header-value-multiline.creq │ │ ├── get-header-value-multiline.req │ │ ├── get-header-value-multiline.sreq │ │ └── get-header-value-multiline.sts │ ├── get-header-value-order │ │ ├── get-header-value-order.authz │ │ ├── get-header-value-order.creq │ │ ├── get-header-value-order.req │ │ ├── get-header-value-order.sreq │ │ └── get-header-value-order.sts │ ├── get-header-value-trim │ │ ├── get-header-value-trim.authz │ │ ├── get-header-value-trim.creq │ │ ├── get-header-value-trim.req │ │ ├── get-header-value-trim.sreq │ │ └── get-header-value-trim.sts │ ├── get-unreserved │ │ ├── get-unreserved.authz │ │ ├── get-unreserved.creq │ │ ├── get-unreserved.req │ │ ├── get-unreserved.sreq │ │ └── get-unreserved.sts │ ├── get-utf8 │ │ ├── get-utf8.authz │ │ ├── get-utf8.creq │ │ ├── get-utf8.req │ │ ├── get-utf8.sreq │ │ └── get-utf8.sts │ ├── get-vanilla-empty-query-key │ │ ├── get-vanilla-empty-query-key.authz │ │ ├── get-vanilla-empty-query-key.creq │ │ ├── get-vanilla-empty-query-key.req │ │ ├── get-vanilla-empty-query-key.sreq │ │ └── get-vanilla-empty-query-key.sts │ ├── get-vanilla-query-order-key-case │ │ ├── get-vanilla-query-order-key-case.authz │ │ ├── get-vanilla-query-order-key-case.creq │ │ ├── get-vanilla-query-order-key-case.req │ │ ├── get-vanilla-query-order-key-case.sreq │ │ └── get-vanilla-query-order-key-case.sts │ ├── get-vanilla-query-order-key │ │ ├── get-vanilla-query-order-key.authz │ │ ├── get-vanilla-query-order-key.creq │ │ ├── get-vanilla-query-order-key.req │ │ ├── get-vanilla-query-order-key.sreq │ │ └── get-vanilla-query-order-key.sts │ ├── get-vanilla-query-order-value │ │ ├── get-vanilla-query-order-value.authz │ │ ├── get-vanilla-query-order-value.creq │ │ ├── get-vanilla-query-order-value.req │ │ ├── get-vanilla-query-order-value.sreq │ │ └── get-vanilla-query-order-value.sts │ ├── get-vanilla-query-unreserved │ │ ├── get-vanilla-query-unreserved.authz │ │ ├── get-vanilla-query-unreserved.creq │ │ ├── get-vanilla-query-unreserved.req │ │ ├── get-vanilla-query-unreserved.sreq │ │ └── get-vanilla-query-unreserved.sts │ ├── get-vanilla-query │ │ ├── get-vanilla-query.authz │ │ ├── get-vanilla-query.creq │ │ ├── get-vanilla-query.req │ │ ├── get-vanilla-query.sreq │ │ └── get-vanilla-query.sts │ ├── get-vanilla-utf8-query │ │ ├── get-vanilla-utf8-query.authz │ │ ├── get-vanilla-utf8-query.creq │ │ ├── get-vanilla-utf8-query.req │ │ ├── get-vanilla-utf8-query.sreq │ │ └── get-vanilla-utf8-query.sts │ ├── get-vanilla │ │ ├── get-vanilla.authz │ │ ├── get-vanilla.creq │ │ ├── get-vanilla.req │ │ ├── get-vanilla.sreq │ │ └── get-vanilla.sts │ ├── normalize-path │ │ ├── get-relative-relative │ │ │ ├── get-relative-relative.authz │ │ │ ├── get-relative-relative.creq │ │ │ ├── get-relative-relative.req │ │ │ ├── get-relative-relative.sreq │ │ │ └── get-relative-relative.sts │ │ ├── get-relative │ │ │ ├── get-relative.authz │ │ │ ├── get-relative.creq │ │ │ ├── get-relative.req │ │ │ ├── get-relative.sreq │ │ │ └── get-relative.sts │ │ ├── get-slash-dot-slash │ │ │ ├── get-slash-dot-slash.authz │ │ │ ├── get-slash-dot-slash.creq │ │ │ ├── get-slash-dot-slash.req │ │ │ ├── get-slash-dot-slash.sreq │ │ │ └── get-slash-dot-slash.sts │ │ ├── get-slash-pointless-dot │ │ │ ├── get-slash-pointless-dot.authz │ │ │ ├── get-slash-pointless-dot.creq │ │ │ ├── get-slash-pointless-dot.req │ │ │ ├── get-slash-pointless-dot.sreq │ │ │ └── get-slash-pointless-dot.sts │ │ ├── get-slash │ │ │ ├── get-slash.authz │ │ │ ├── get-slash.creq │ │ │ ├── get-slash.req │ │ │ ├── get-slash.sreq │ │ │ └── get-slash.sts │ │ ├── get-slashes │ │ │ ├── get-slashes.authz │ │ │ ├── get-slashes.creq │ │ │ ├── get-slashes.req │ │ │ ├── get-slashes.sreq │ │ │ └── get-slashes.sts │ │ ├── get-space │ │ │ ├── get-space.authz │ │ │ ├── get-space.creq │ │ │ ├── get-space.req │ │ │ ├── get-space.sreq │ │ │ └── get-space.sts │ │ └── normalize-path.txt │ ├── post-header-key-case │ │ ├── post-header-key-case.authz │ │ ├── post-header-key-case.creq │ │ ├── post-header-key-case.req │ │ ├── post-header-key-case.sreq │ │ └── post-header-key-case.sts │ ├── post-header-key-sort │ │ ├── post-header-key-sort.authz │ │ ├── post-header-key-sort.creq │ │ ├── post-header-key-sort.req │ │ ├── post-header-key-sort.sreq │ │ └── post-header-key-sort.sts │ ├── post-header-value-case │ │ ├── post-header-value-case.authz │ │ ├── post-header-value-case.creq │ │ ├── post-header-value-case.req │ │ ├── post-header-value-case.sreq │ │ └── post-header-value-case.sts │ ├── post-sts-token │ │ ├── post-sts-header-after │ │ │ ├── post-sts-header-after.authz │ │ │ ├── post-sts-header-after.creq │ │ │ ├── post-sts-header-after.req │ │ │ ├── post-sts-header-after.sreq │ │ │ └── post-sts-header-after.sts │ │ ├── post-sts-header-before │ │ │ ├── post-sts-header-before.authz │ │ │ ├── post-sts-header-before.creq │ │ │ ├── post-sts-header-before.req │ │ │ ├── post-sts-header-before.sreq │ │ │ └── post-sts-header-before.sts │ │ └── readme.txt │ ├── post-vanilla-empty-query-value │ │ ├── post-vanilla-empty-query-value.authz │ │ ├── post-vanilla-empty-query-value.creq │ │ ├── post-vanilla-empty-query-value.req │ │ ├── post-vanilla-empty-query-value.sreq │ │ └── post-vanilla-empty-query-value.sts │ ├── post-vanilla-query │ │ ├── post-vanilla-query.authz │ │ ├── post-vanilla-query.creq │ │ ├── post-vanilla-query.req │ │ ├── post-vanilla-query.sreq │ │ └── post-vanilla-query.sts │ ├── post-vanilla │ │ ├── post-vanilla.authz │ │ ├── post-vanilla.creq │ │ ├── post-vanilla.req │ │ ├── post-vanilla.sreq │ │ └── post-vanilla.sts │ ├── post-x-www-form-urlencoded-parameters │ │ ├── post-x-www-form-urlencoded-parameters.authz │ │ ├── post-x-www-form-urlencoded-parameters.creq │ │ ├── post-x-www-form-urlencoded-parameters.req │ │ ├── post-x-www-form-urlencoded-parameters.sreq │ │ └── post-x-www-form-urlencoded-parameters.sts │ └── post-x-www-form-urlencoded │ │ ├── post-x-www-form-urlencoded.authz │ │ ├── post-x-www-form-urlencoded.creq │ │ ├── post-x-www-form-urlencoded.req │ │ ├── post-x-www-form-urlencoded.sreq │ │ └── post-x-www-form-urlencoded.sts ├── botocore │ └── protocols │ │ ├── input │ │ ├── ec2.json │ │ ├── json.json │ │ ├── query.json │ │ ├── rest-json.json │ │ └── rest-xml.json │ │ └── output │ │ ├── ec2.json │ │ ├── event-stream.json │ │ ├── json.json │ │ ├── query.json │ │ ├── rest-json.json │ │ └── rest-xml.json └── cognitect │ └── protocols │ ├── input │ ├── query.json │ └── rest-xml.json │ └── output │ ├── json.json │ ├── rest-json.json │ └── rest-xml.json └── src └── com └── grzm └── awyeah ├── client ├── api │ ├── localstack_elf.clj │ └── localstack_test.clj ├── api_test.clj ├── impl_test.cljc └── test_double_test.clj ├── config_test.clj ├── credentials_test.cljc ├── ec2_metadata_utils_test.clj ├── endpoint_test.clj ├── generators.clj ├── integration ├── error_codes_test.clj └── s3_test.clj ├── interceptors_test.clj ├── jdk.clj ├── protocols └── rest_test.clj ├── protocols_test.clj ├── region_test.clj ├── retry_test.clj ├── shape_test.clj ├── signers_test.clj ├── test.cljc ├── test ├── ec2_metadata_utils_server.clj └── utils.clj └── util_test.clj /.clj-kondo/babashka/fs/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:cljc {:features [:clj]} 2 | :linters {:unused-private-var {:exclude [com.grzm.awyeah.client.shared/shared-http-client?]}}} 3 | -------------------------------------------------------------------------------- /.clj-kondo/http-kit/http-kit/config.edn: -------------------------------------------------------------------------------- 1 | 2 | {:hooks 3 | {:analyze-call {org.httpkit.server/with-channel httpkit.with-channel/with-channel}}} 4 | -------------------------------------------------------------------------------- /.clj-kondo/http-kit/http-kit/httpkit/with_channel.clj: -------------------------------------------------------------------------------- 1 | (ns httpkit.with-channel 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn with-channel [{node :node}] 5 | (let [[request channel & body] (rest (:children node))] 6 | (when-not (and request channel) (throw (ex-info "No request or channel provided" {}))) 7 | (when-not (api/token-node? channel) (throw (ex-info "Missing channel argument" {}))) 8 | (let [new-node 9 | (api/list-node 10 | (list* 11 | (api/token-node 'let) 12 | (api/vector-node [channel (api/vector-node [])]) 13 | request 14 | body))] 15 | 16 | {:node new-node}))) 17 | -------------------------------------------------------------------------------- /.clj-kondo/rewrite-clj/rewrite-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {rewrite-clj.zip/subedit-> clojure.core/-> 3 | rewrite-clj.zip/subedit->> clojure.core/->> 4 | rewrite-clj.zip/edit-> clojure.core/-> 5 | rewrite-clj.zip/edit->> clojure.core/->>}} 6 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((cider-clojure-cli-aliases . ":clj")))) 2 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Thank you for your interest in awyeah-api. 2 | 3 | This project does not accept pull requests. 4 | 5 | If you have an issue, [please open one](https://github.com/grzm/awyeah-api/issues). 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | .clj-kondo/.cache 3 | .java-version 4 | etc/volume 5 | -------------------------------------------------------------------------------- /CHANGES.markdown: -------------------------------------------------------------------------------- 1 | # awyeah-api 2 | 3 | ## 0.8.84 / e551334 / 2023-12-02 4 | 5 | * Now requires babashka v1.1.170 or later. 6 | * No longer requires separate installation of babashka/spec.alpha (https://github.com/grzm/awyeah-api/issues/14). 7 | Thanks, Dan Meyers, for the report! 8 | * Force HTTP client to use HTTP Version 1.1 9 | Fixes invalid request signing for AWS services that support HTTP 2 (https://github.com/grzm/awyeah-api/issues/8) 10 | Thanks, Josh Glover, for the detailed report! 11 | 12 | ## 0.8.82 / 5ecad02 / 2023-11-14 13 | 14 | * Implement aws-api cached-credentials-with-auto-refresh 15 | 16 | ## 0.8.79 / 0399fec / 2023-11-12 17 | 18 | * Catch up to aws-api 0.8.686 (2023-07-11) 19 | * Also include aws-api refactoring commits up to 5900e35 (2023-07-12) 20 | 21 | ## 0.8.48 / 084f671 / 2023-09-25 22 | 23 | * Fix spelling of :patch in http client implementation 24 | Thanks, Dan Meyers! 25 | 26 | ## 0.8.41 / 9257dc0 / 2022-10-13 27 | 28 | * Catch up to aws-api 0.8.603 (2022-10-11) 29 | * Port remainder of aws-api tests. 30 | 31 | ## 0.8.35 / 1810bf6 / 2022-10-08 32 | 33 | * Catch up to aws-api 0.8.596. 34 | * Fix handling of HEAD HTTP requests (such as S3 HeadObject). 35 | * Fix broken client datafy compatibility. 36 | * Use GMT instead of UTC for timestamp formatting. 37 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # awyeah-api - aws-api for babashka 2 | 3 | Cognitect's [aws-api][] and [babashka][]. Aw yeah. 4 | 5 | awyeah-api is _another_ Clojure library which provides programmatic 6 | access to AWS services from your Clojure **or babashka** program. Its 7 | _raison d'être_ is to be a drop-in replacement for Cognitect's 8 | brilliant [aws-api][] that will work from source with [babashka][]. 9 | 10 | awyeah-api should work with `com.cognitect.aws/endpoints` and 11 | `com.cognitect.aws` service packages. 12 | 13 | * [aws-api][] 14 | * [API Docs](https://cognitect-labs.github.io/aws-api/) 15 | 16 | [aws-api]: https://github.com/cognitect-labs/aws-api 17 | [babashka]: https://babashka.org 18 | 19 | ### Acknowledgements (and disclaimer) 20 | 21 | This port is based on the great work done by the fine folks at 22 | Cognitect. I am not affiliated with Cognitect and this is not a 23 | Cognitect-distributed library. 24 | 25 | ## Requirements 26 | 27 | Requires babashka v1.0.170 or later (java.util.concurrent.ThreadFactory support). 28 | 29 | ## deps 30 | ``` clojure 31 | com.grzm/awyeah-api {:git/url "https://github.com/grzm/awyeah-api" 32 | :git/sha "e5513349a2fd8a980a62bbe0d45a0d55bfcea141" 33 | :git/tag "v0.8.84"} 34 | ``` 35 | 36 | Up to date with `com.cognitect.aws/api {:mvn/version "0.8.686"}` (tagged 2023-07-11). 37 | 38 | ## In brief 39 | 40 | Add to your `deps.edn` for Clojure or `bb.edn` for babashka: 41 | 42 | ### `deps.edn` 43 | ```clojure 44 | {:deps {com.cognitect.aws/endpoints {:mvn/version "1.1.12.504"} 45 | com.cognitect.aws/s3 {:mvn/version "848.2.1413.0"} 46 | com.grzm/awyeah-api {:git/url "https://github.com/grzm/awyeah-api" 47 | :git/sha "e5513349a2fd8a980a62bbe0d45a0d55bfcea141" 48 | :git/tag "v0.8.84"} 49 | ;; dependencies included in babashka 50 | cheshire/cheshire {:mvn/version "5.12.0"} 51 | org.clojure/core.async {:mvn/version "1.6.681"} 52 | org.clojure/data.xml {:mvn/version "0.2.0-alpha8"} 53 | org.clojure/tools.logging {:mvn/version "1.2.4"}}} 54 | ``` 55 | 56 | ### `bb.edn` 57 | ``` clojure 58 | {:deps {com.cognitect.aws/endpoints {:mvn/version "1.1.12.504"} 59 | com.cognitect.aws/s3 {:mvn/version "848.2.1413.0"} 60 | com.grzm/awyeah-api {:git/url "https://github.com/grzm/awyeah-api" 61 | :git/sha "e5513349a2fd8a980a62bbe0d45a0d55bfcea141" 62 | :git/tag "v0.8.84"}}} 63 | ``` 64 | 65 | ```clojure 66 | (require '[com.grzm.awyeah.client.api :as aws]) 67 | 68 | (def s3 (aws/client {:api :s3})) 69 | 70 | (def buckets (-> (aws/invoke s3 {:op :ListBuckets}) 71 | :Buckets)) 72 | 73 | (prn buckets) 74 | ``` 75 | 76 | ## Differences from aws-api 77 | 78 | The [com.cognitect/http-client][] used by aws-api automatically 79 | uncompresses S3 objects if they have the appropriate content-type 80 | metadata in S3. I've chosen _not_ to replicate this behavior with the 81 | bundled HTTP client based on `java.net.http` as I think it's a bit of 82 | a misfeature to have enabled by default in a programmatic client 83 | library like this. (That said, that makes it less of a drop-in 84 | replacement, doesn't it? I may revisit this.) 85 | 86 | [com.cognitect/http-client]: https://search.maven.org/artifact/com.cognitect/http-client 87 | 88 | See [Porting Decisions](docs/porting-decisions.markdown) if you're 89 | interested in the nitty gritty or are wondering why I made some 90 | decision or other. 91 | 92 | ## Development 93 | 94 | Get thee to a repl! 95 | 96 | ```sh 97 | bb --classpath $(clojure -Spath -A:bb:dev:test) nrepl-server 1138 98 | ``` 99 | 100 | ## Testing 101 | 102 | Run the tests against a [LocalStack](https://localstack.cloud) Docker 103 | container. 104 | 105 | ```sh 106 | bin/dc up 107 | bin/test 108 | bin/dc down 109 | ``` 110 | 111 | ### Exclude Localstack tests 112 | 113 | Sometimes you want to run tests but you don't want to stand up 114 | Localstack. This is currently convenient only with JVM Clojure. 115 | 116 | ```sh 117 | clj -X:clj:dev:test :excludes '[:integration]' 118 | ``` 119 | 120 | Some additional testing notes so I don't have to look up each time I 121 | do awyeah-api maintenance. 122 | 123 | Select namespaces to test: 124 | 125 | ```sh 126 | clj -X:clj:dev:test :nses '[com.grzm.awyeah.util-test]' 127 | ``` 128 | 129 | #### Testing aws-api 130 | 131 | It's useful to run aws-api tests to compare behavior. These are run in 132 | an aws-api repo checkout. 133 | 134 | Selecting namespaces to test: 135 | 136 | ```sh 137 | clj -M:dev:test -n cognitect.aws.util-test 138 | ``` 139 | 140 | ## Thanks 141 | 142 | Much thanks to [Michiel Borkent](https://michielborkent.nl) for 143 | babashka and providing support and guidance in getting the pieces in 144 | place (java.net.http and various other Java classes) to make this library possible. 145 | 146 | ## Copyright and License 147 | 148 | Mostly © 2015 Cognitect 149 | 150 | Parts © 2022–2023 Michael Glaesemann 151 | 152 | Licensed under the Apache License, Version 2.0 (the "License"); 153 | you may not use this file except in compliance with the License. 154 | You may obtain a copy of the License at 155 | 156 | http://www.apache.org/licenses/LICENSE-2.0 157 | 158 | Unless required by applicable law or agreed to in writing, software 159 | distributed under the License is distributed on an "AS IS" BASIS, 160 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 161 | See the License for the specific language governing permissions and 162 | limitations under the License. 163 | -------------------------------------------------------------------------------- /VERSION_PREFIX: -------------------------------------------------------------------------------- 1 | 0.8 -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:paths ["resources" "src" "test/resources" "test/src"] 2 | :deps 3 | {org.babashka/spec.alpha {:git/url "https://github.com/babashka/spec.alpha" 4 | :git/sha "1a841c4cc1d4f6dab7505a98ed2d532dd9d56b78"} 5 | com.cognitect.aws/ec2 6 | {:mvn/version "848.2.1413.0", :aws/serviceFullName "Amazon Elastic Compute Cloud"}, 7 | com.cognitect.aws/endpoints {:mvn/version "1.1.12.504"} 8 | com.cognitect.aws/lambda {:mvn/version "848.2.1413.0", :aws/serviceFullName "AWS Lambda"}, 9 | com.cognitect.aws/s3 10 | {:mvn/version "848.2.1413.0", :aws/serviceFullName "Amazon Simple Storage Service"} 11 | com.cognitect.aws/ssm 12 | {:mvn/version "848.2.1413.0", :aws/serviceFullName "Amazon Simple Systems Manager (SSM)"} 13 | com.cognitect.aws/sts 14 | {:mvn/version "847.2.1387.0", :aws/serviceFullName "AWS Security Token Service"}}} 15 | -------------------------------------------------------------------------------- /bin/dc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | localstack_port=4566 5 | 6 | repo_root="$(git rev-parse --show-toplevel)" 7 | docker_compose_yaml="${repo_root}/etc/docker-compose.yaml" 8 | 9 | _up () { 10 | docker compose -f "${docker_compose_yaml}" up --pull always --detach --wait 11 | } 12 | 13 | op="${1}" 14 | 15 | case "${op}" in 16 | up) _up 17 | ;; 18 | *) docker compose -f "${docker_compose_yaml}" $@ 19 | ;; 20 | esac; 21 | -------------------------------------------------------------------------------- /bin/lint: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -uo pipefail 4 | 5 | repo_root="$(git rev-parse --show-toplevel)" 6 | 7 | cd "${repo_root}" 8 | 9 | clj-kondo --lint src 10 | clj-kondo --lint test 11 | ack --ignore-dir=bin \ 12 | --ignore-dir=.lsp \ 13 | --ignore-dir=.clj-kondo \ 14 | --ignore-dir=.cpcache \ 15 | --ignore-dir=docs \ 16 | --ignore-file=is:README.markdown \ 17 | --ignore-file=is:deps.edn \ 18 | 'cognitect\.aws' 19 | -------------------------------------------------------------------------------- /bin/revision: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Returns the revision number used for deployment. 4 | 5 | set -eu 6 | 7 | revision=$(git --no-replace-objects describe --tags --match rev0) 8 | 9 | # Extract the version number from the string. Do this in two steps so 10 | # it is a little easier to understand. 11 | revision=${revision:5} # drop the first 5 characters 12 | revision=${revision:0:${#revision}-9} # drop the last 9 characters 13 | 14 | echo "${revision}" 15 | -------------------------------------------------------------------------------- /bin/tag: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | repo_root="$(git rev-parse --show-toplevel)" 6 | 7 | cd "${repo_root}" 8 | 9 | prefix=$(cat VERSION_PREFIX) 10 | suffix=$(bin/revision) 11 | version="${prefix}.${suffix}" 12 | 13 | git tag -a "v${version}" -m "Release ${version}" 14 | -------------------------------------------------------------------------------- /bin/test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | repo_root="$(git rev-parse --show-toplevel)" 6 | 7 | cd "${repo_root}" 8 | if [[ -z ${SKIP_BB+x} ]] ; then 9 | echo "bb tests" 10 | bb --main com.grzm.awyeah.test/run-tests 11 | else 12 | echo "skipping bb tests" 13 | fi 14 | 15 | echo 16 | echo "clj tests" 17 | clj -X:dev:test:clj 18 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources"] 2 | :aliases 3 | {:clj 4 | {:extra-deps 5 | {cheshire/cheshire {:mvn/version "5.12.0"} 6 | http-kit/http-kit {:mvn/version "2.7.0"} 7 | org.clojure/core.async {:mvn/version "1.6.681"} 8 | org.clojure/data.xml {:mvn/version "0.2.0-alpha8"} 9 | org.clojure/tools.logging {:mvn/version "1.2.4"}} 10 | :jvm-opts ["-Djdk.httpclient.allowRestrictedHeaders=host"]} 11 | :dev 12 | {:extra-paths ["dev"] 13 | :extra-deps 14 | {com.amazonaws/aws-java-sdk {:mvn/version "1.12.344"} 15 | com.cognitect.aws/ec2 16 | {:mvn/version "848.2.1413.0", :aws/serviceFullName "Amazon Elastic Compute Cloud"}, 17 | com.cognitect.aws/endpoints {:mvn/version "1.1.12.504"} 18 | com.cognitect.aws/lambda {:mvn/version "848.2.1413.0", :aws/serviceFullName "AWS Lambda"}, 19 | com.cognitect.aws/s3 20 | {:mvn/version "848.2.1413.0", :aws/serviceFullName "Amazon Simple Storage Service"} 21 | com.cognitect.aws/ssm 22 | {:mvn/version "848.2.1413.0", :aws/serviceFullName "Amazon Simple Systems Manager (SSM)"} 23 | com.cognitect.aws/sts 24 | {:mvn/version "847.2.1387.0", :aws/serviceFullName "AWS Security Token Service"}}} 25 | :test 26 | {:extra-paths ["test/resources" "test/src"] 27 | :extra-deps {commons-io/commons-io {:mvn/version "2.15.0"} 28 | io.github.cognitect-labs/test-runner {:git/tag "v0.5.1" :git/sha "dfb30dd"} 29 | org.clojure/test.check {:mvn/version "1.1.1"}} 30 | :exec-fn cognitect.test-runner.api/test} 31 | :lint-ns 32 | {:replace-deps {com.github.clojure-lsp/clojure-lsp {:mvn/version "2023.10.30-16.25.41"}} 33 | :exec-fn clojure-lsp.api/clean-ns! 34 | :exec-args {:dry? true}}}} 35 | -------------------------------------------------------------------------------- /dev/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((clojure-mode . ((cider-clojure-cli-aliases . "-:dev:aws-api:clj") 2 | (cider-preferred-build-tool . clojure-cli)))) 3 | -------------------------------------------------------------------------------- /dev/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [com.grzm.awyeah.client.api :as aws] 4 | [com.grzm.awyeah.client :as client] 5 | [com.grzm.awyeah.credentials :as credentials] 6 | [com.grzm.awyeah.endpoint :as endpoint])) 7 | 8 | ;; localstack 9 | (def client-default-opts 10 | {:credentials-provider (credentials/basic-credentials-provider 11 | {:access-key-id "ABC" 12 | :secret-access-key "XYZ"}) 13 | :endpoint-override {:protocol :http 14 | :hostname "localhost" 15 | :port 4566}}) 16 | 17 | (defn client [opts] 18 | (aws/client (merge client-default-opts opts))) 19 | 20 | (comment 21 | 22 | (def sts (aws/client {:api :sts})) 23 | (def sts (client {:api :sts})) 24 | 25 | (aws/invoke sts {:op :GetCallerIdentity}) 26 | 27 | (keys (client/-get-info sts)) 28 | (def client-info (client/-get-info sts)) 29 | (:endpoint-provider client-info) 30 | (def service (:service (client/-get-info sts))) 31 | (def req-map {:op :GetCallerIdentity}) 32 | (client/build-http-request service req-map) 33 | (endpoint/default-endpoint-provider (get-in service [:metadata :endpointPrefix]) {}) 34 | 35 | 36 | (def s3 (aws/client {:api :s3})) 37 | (aws/invoke s3 {:op :ListBuckets}) 38 | (def ec2 (aws/client {:api :ec2, :region "eu-west-1"})) 39 | (aws/invoke ec2 {:op :DescribeInstances}) 40 | 41 | (def lambda (aws/client {:api :lambda})) 42 | (aws/invoke lambda {:op :ListFunctions}) 43 | (def ssm (aws/client {:api :ssm})) 44 | 45 | (def s3 (client {:api :s3})) 46 | 47 | (aws/invoke sts {:op :GetCallerIdentity}) 48 | 49 | (def s3 (aws/client {:api :s3})) 50 | 51 | (def req-map {:op :ListBuckets}) 52 | (:service (client/-get-info s3)) 53 | (do (println "----") 54 | (client/build-http-request (:service (client/-get-info s3)) req-map)) 55 | (aws/invoke s3 req-map) 56 | 57 | :end) 58 | 59 | -------------------------------------------------------------------------------- /etc/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | localstack: 5 | container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}" 6 | image: localstack/localstack:2.3.2 7 | network_mode: bridge 8 | ports: 9 | - "127.0.0.1:4510-4559:4510-4559" # external service port range 10 | - "127.0.0.1:4566:4566" # LocalStack Edge Proxy 11 | environment: 12 | - DEBUG=${DEBUG-} 13 | - DOCKER_HOST=unix:///var/run/docker.sock 14 | # events service needed for ssm PutParameter 15 | - LOCALSTACK_SERVICES=ec2,events,lambda,s3,ssm,sts 16 | volumes: 17 | - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack" 18 | - "/var/run/docker.sock:/var/run/docker.sock" 19 | -------------------------------------------------------------------------------- /resources/com_grzm_awyeah_http.edn: -------------------------------------------------------------------------------- 1 | {:constructor-var com.grzm.awyeah.http.awyeah/create} 2 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/client.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.client 5 | "Impl, don't call directly." 6 | (:require 7 | [clojure.core.async :as a] 8 | [com.grzm.awyeah.credentials :as credentials] 9 | [com.grzm.awyeah.endpoint :as endpoint] 10 | [com.grzm.awyeah.http :as http] 11 | [com.grzm.awyeah.interceptors :as interceptors] 12 | [com.grzm.awyeah.region :as region] 13 | [com.grzm.awyeah.util :as util])) 14 | 15 | (set! *warn-on-reflection* true) 16 | 17 | (defprotocol ClientSPI 18 | (-get-info [_] "Intended for internal use only")) 19 | 20 | (defrecord Client [info] 21 | ClientSPI 22 | (-get-info [_] info)) 23 | 24 | (defn client [client-meta info] 25 | (let [region (some-> info :region-provider region/fetch)] 26 | (-> (with-meta (->Client info) @client-meta) 27 | (assoc :region region 28 | :endpoint (some-> info :endpoint-provider (endpoint/fetch region)) 29 | :credentials (some-> info :credentials-provider credentials/fetch) 30 | :service (some-> info :service (select-keys [:metadata])) 31 | :http-client (:http-client info))))) 32 | 33 | (defmulti build-http-request 34 | "AWS request -> HTTP request." 35 | (fn [service _op-map] 36 | (get-in service [:metadata :protocol]))) 37 | 38 | (defmulti parse-http-response 39 | "HTTP response -> AWS response" 40 | (fn [service _op-map _http-response] 41 | (get-in service [:metadata :protocol]))) 42 | 43 | (defmulti sign-http-request 44 | "Sign the HTTP request." 45 | (fn [service _endpoint _credentials _http-request] 46 | (get-in service [:metadata :signatureVersion]))) 47 | 48 | ;; TODO convey throwable back from impl 49 | (defn ^:private handle-http-response 50 | [service op-map http-response] 51 | (try 52 | (if (:cognitect.anomalies/category http-response) 53 | http-response 54 | (parse-http-response service op-map http-response)) 55 | (catch Throwable t 56 | {:cognitect.anomalies/category :cognitect.anomalies/fault 57 | ::throwable t}))) 58 | 59 | (defn ^:private with-endpoint [req {:keys [protocol hostname port path]}] 60 | (cond-> (-> req 61 | (assoc-in [:headers "host"] hostname) 62 | (assoc :server-name hostname)) 63 | protocol (assoc :scheme protocol) 64 | port (assoc :server-port port) 65 | path (assoc :uri path))) 66 | 67 | (defn ^:private put-throwable [result-ch t response-meta op-map] 68 | (a/put! result-ch (with-meta 69 | {:cognitect.anomalies/category :cognitect.anomalies/fault 70 | ::throwable t} 71 | (swap! response-meta 72 | assoc :op-map op-map)))) 73 | 74 | (defn send-request 75 | "For internal use. Send the request to AWS and return a channel which delivers the response. 76 | 77 | Alpha. Subject to change." 78 | [client op-map] 79 | (let [{:keys [service http-client region-provider credentials-provider endpoint-provider]} 80 | (-get-info client) 81 | response-meta (atom {}) 82 | region-ch (region/fetch-async region-provider) 83 | creds-ch (credentials/fetch-async credentials-provider) 84 | response-ch (a/chan 1) 85 | result-ch (a/promise-chan)] 86 | (a/go 87 | (let [region (a/! result-ch region) 93 | (:cognitect.anomalies/category creds) 94 | (a/>! result-ch creds) 95 | (:cognitect.anomalies/category endpoint) 96 | (a/>! result-ch endpoint) 97 | :else 98 | (try 99 | (let [req (-> (build-http-request service op-map) 100 | (with-endpoint endpoint) 101 | (update :body util/->bbuf) 102 | ((partial interceptors/modify-http-request service op-map))) 103 | http-request (sign-http-request service endpoint 104 | creds 105 | req)] 106 | (swap! response-meta assoc :http-request http-request) 107 | (http/submit http-client http-request response-ch)) 108 | (catch Throwable t 109 | (put-throwable result-ch t response-meta op-map)))))) 110 | (a/go 111 | (try 112 | (let [response (a/! result-ch (with-meta 114 | (handle-http-response service op-map response) 115 | (swap! response-meta assoc 116 | :http-response (update response :body util/bbuf->input-stream))))) 117 | (catch Throwable t 118 | (put-throwable result-ch t response-meta op-map)))) 119 | result-ch)) 120 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/client/api/async.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.client.api.async 5 | "DEPRECATED" 6 | (:require 7 | [com.grzm.awyeah.client.protocol :as client.protocol])) 8 | 9 | (defn ^:deprecated invoke 10 | "DEPRECATED: use com.grzm.awyeah.client.api/invoke-async instead" 11 | [client op-map] 12 | (client.protocol/-invoke-async client op-map)) 13 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/client/impl.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.client.impl 5 | "Impl, don't call directly." 6 | (:require 7 | [clojure.core.async :as a] 8 | [com.grzm.awyeah.client.protocol :as client.protocol] 9 | [com.grzm.awyeah.client.shared :as shared] 10 | [com.grzm.awyeah.client.validation :as validation] 11 | [com.grzm.awyeah.credentials :as credentials] 12 | [com.grzm.awyeah.endpoint :as endpoint] 13 | [com.grzm.awyeah.http :as http] 14 | [com.grzm.awyeah.interceptors :as interceptors] 15 | [com.grzm.awyeah.protocols :as aws.protocols] 16 | [com.grzm.awyeah.region :as region] 17 | [com.grzm.awyeah.retry :as retry] 18 | [com.grzm.awyeah.signers :as signers] 19 | [com.grzm.awyeah.util :as util])) 20 | 21 | (set! *warn-on-reflection* true) 22 | 23 | ;; TODO convey throwable back from impl 24 | (defn ^:private handle-http-response 25 | [service op-map {:keys [status] :as http-response}] 26 | (try 27 | (if (:cognitect.anomalies/category http-response) 28 | http-response 29 | (if (< status 300) 30 | (aws.protocols/parse-http-response service op-map http-response) 31 | (aws.protocols/parse-http-error-response http-response))) 32 | (catch Throwable t 33 | {:cognitect.anomalies/category :cognitect.anomalies/fault 34 | ::throwable t}))) 35 | 36 | (defn ^:private with-endpoint [req {:keys [protocol hostname port path]}] 37 | (cond-> (-> req 38 | (assoc-in [:headers "host"] hostname) 39 | (assoc :server-name hostname)) 40 | protocol (assoc :scheme protocol) 41 | port (assoc :server-port port) 42 | path (assoc :uri path))) 43 | 44 | (defn ^:private put-throwable [result-ch t response-meta op-map] 45 | (a/put! result-ch (with-meta 46 | {:cognitect.anomalies/category :cognitect.anomalies/fault 47 | ::throwable t} 48 | (swap! response-meta 49 | assoc :op-map op-map)))) 50 | 51 | (defn ^:private send-request 52 | [client op-map http-request] 53 | (let [{:keys [service http-client region-provider credentials-provider endpoint-provider]} 54 | (client.protocol/-get-info client) 55 | response-meta (atom {}) 56 | region-ch (region/fetch-async region-provider) 57 | creds-ch (credentials/fetch-async credentials-provider) 58 | response-ch (a/chan 1) 59 | result-ch (a/promise-chan)] 60 | (a/go 61 | (let [region (a/! result-ch region) 67 | (:cognitect.anomalies/category creds) 68 | (a/>! result-ch creds) 69 | (:cognitect.anomalies/category endpoint) 70 | (a/>! result-ch endpoint) 71 | :else 72 | (try 73 | (let [http-request (signers/sign-http-request service endpoint 74 | creds 75 | (-> http-request 76 | (with-endpoint endpoint) 77 | ((partial interceptors/modify-http-request service op-map))))] 78 | (swap! response-meta assoc :http-request http-request) 79 | (http/submit http-client http-request response-ch)) 80 | (catch Throwable t 81 | (put-throwable result-ch t response-meta op-map)))))) 82 | (a/go 83 | (try 84 | (let [response (a/! result-ch (with-meta 86 | (handle-http-response service op-map response) 87 | (swap! response-meta assoc 88 | :http-response (update response :body util/bbuf->input-stream))))) 89 | (catch Throwable t 90 | (put-throwable result-ch t response-meta op-map)))) 91 | result-ch)) 92 | 93 | (defrecord Client [info] 94 | client.protocol/Client 95 | (-get-info [_] info) 96 | 97 | (-invoke [client op-map] 98 | (a/ (aws.protocols/build-http-request service op-map) 115 | (update :body util/->bbuf))] 116 | (retry/with-retry 117 | #(send-request client op-map req) 118 | result-chan 119 | (or (:retriable? op-map) retriable?) 120 | (or (:backoff op-map) backoff)))) 121 | 122 | result-chan)) 123 | 124 | (-stop [aws-client] 125 | (let [{:keys [http-client]} (client.protocol/-get-info aws-client)] 126 | (when-not (#'shared/shared-http-client? http-client) 127 | (http/stop http-client))))) 128 | 129 | ;; ->Client is intended for internal use 130 | (alter-meta! #'->Client assoc :skip-wiki true) 131 | 132 | (defn client [client-meta info] 133 | (let [region (some-> info :region-provider region/fetch)] 134 | (-> (with-meta (->Client info) @client-meta) 135 | (assoc :api (-> info :service :metadata :cognitect.aws/service-name) 136 | :region region 137 | :endpoint (some-> info :endpoint-provider (endpoint/fetch region)) 138 | :credentials (some-> info :credentials-provider credentials/fetch) 139 | :service (some-> info :service (select-keys [:metadata])) 140 | :http-client (:http-client info))))) 141 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/client/protocol.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.client.protocol 5 | "Impl, don't call directly.") 6 | 7 | (defprotocol Client 8 | (-get-info [_] "Used by fns in com.grzm.awyeah.client.api. Implementors must supply 9 | :service, and may also supply anything else needed by the other 10 | protocol functions. 11 | 12 | The com.grzm.awyeah.client.api/client uses the following: 13 | :retriable? 14 | :backoff 15 | :http-client 16 | :endpoint-provider 17 | :region-provider 18 | :credentials-provider 19 | :validate-requests?") 20 | (-invoke [this op-map] "Packages and ships a request and returns a response") 21 | (-invoke-async [this op-map] "Packages and ships a request and returns a channel that will contain a response") 22 | (-stop [this] "Release resources managed by this client")) 23 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/client/shared.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.client.shared 5 | (:require 6 | [com.grzm.awyeah.credentials :as credentials] 7 | [com.grzm.awyeah.http :as http] 8 | [com.grzm.awyeah.region :as region])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (declare http-client) 13 | 14 | (def ^:private shared-http-client 15 | (delay (http/resolve-http-client nil))) 16 | 17 | (def ^:private shared-credentials-provider 18 | (delay (credentials/default-credentials-provider (http-client)))) 19 | 20 | (def ^:private shared-region-provider 21 | (delay (region/default-region-provider (http-client)))) 22 | 23 | (defn http-client 24 | "Returns the globally shared instance of http-client (created on the 25 | first call). 26 | 27 | Alpha. Subject to change." 28 | [] 29 | @shared-http-client) 30 | 31 | (defn credentials-provider 32 | "Returns the globally shared instance of credentials-provider, which 33 | uses the globally shared instance of http-client. 34 | 35 | Alpha. Subject to change." 36 | [] 37 | @shared-credentials-provider) 38 | 39 | (defn region-provider 40 | "Returns the globally shared instance of region-provider, which 41 | uses the globally shared instance of http-client. 42 | 43 | Alpha. Subject to change." 44 | [] 45 | @shared-region-provider) 46 | 47 | (defn ^:private shared-http-client? 48 | "For internal use. 49 | 50 | Alpha. Subject to change." 51 | [candidate-http-client] 52 | (identical? candidate-http-client 53 | (and (realized? shared-http-client) @shared-http-client))) 54 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/client/test_double.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.client.test-double 5 | "Provides a test implementation of the aws client, which can be passed 6 | to the functions in the com.grzm.awyeah.client.api ns." 7 | (:require 8 | [clojure.core.async :as a] 9 | [com.grzm.awyeah.client.protocol :as client.protocol] 10 | [com.grzm.awyeah.client.validation :as validation] 11 | [com.grzm.awyeah.service :as service])) 12 | 13 | (set! *warn-on-reflection* true) 14 | 15 | (defn ^:private no-handler-provided-anomaly [op] 16 | {:cognitect.anomalies/category :cognitect.anomalies/incorrect 17 | :cognitect.anomalies/message "No handler or response provided for op" 18 | :op op}) 19 | 20 | (defprotocol TestDoubleClient 21 | (-instrument [c ops])) 22 | 23 | (defrecord Client [info handlers] 24 | client.protocol/Client 25 | (-get-info [_] info) 26 | 27 | (-invoke [this {:keys [op request] :as op-map}] 28 | (let [spec (validation/request-spec (:service info) op) 29 | handler (get @handlers op)] 30 | (cond 31 | (not (get-in info [:service :operations op])) 32 | (validation/unsupported-op-anomaly (:service info) op) 33 | 34 | (and (validation/validate-requests? this) 35 | spec 36 | (not (validation/valid? spec request))) 37 | (validation/invalid-request-anomaly spec request) 38 | 39 | (not handler) 40 | (no-handler-provided-anomaly op) 41 | 42 | :else 43 | (handler op-map)))) 44 | 45 | (-invoke-async [this {:keys [ch] :as op-map}] 46 | (let [response-chan (or ch (a/promise-chan))] 47 | (a/go 48 | (let [resp (client.protocol/-invoke this op-map)] 49 | (a/>! response-chan resp))) 50 | response-chan)) 51 | 52 | (-stop [_aws-client]) 53 | 54 | TestDoubleClient 55 | (-instrument [client ops] 56 | (swap! (:handlers client) 57 | (fn [handlers] 58 | (reduce-kv 59 | (fn [m op handler] 60 | (when-not (some-> client :service :operations op) 61 | (throw (ex-info "Operation not supported" 62 | (validation/unsupported-op-anomaly (-> client :service) op)))) 63 | (assoc m op (if (fn? handler) handler (constantly handler)))) 64 | handlers 65 | ops))))) 66 | 67 | ;; ->Client is intended for internal use 68 | (alter-meta! #'->Client assoc :skip-wiki true) 69 | (alter-meta! #'TestDoubleClient assoc :skip-wiki true) 70 | 71 | (defn instrument 72 | "Given a test double client and a `:ops` map of operations to handlers, 73 | instruments the client with handlers. See `client` for more info about 74 | `:ops`." 75 | [client ops] 76 | (-instrument client ops)) 77 | 78 | (defn client 79 | "Given a map with :api and :ops (optional), returns a test double 80 | client that you can pass to `com.grzm.awyeah.client.api/invoke` and 81 | `com.grzm.awyeah.client.api/stop` in implementation code. 82 | 83 | You can provide :ops on creation or use `instrument` to add them later. 84 | 85 | :ops should be a map of operation (keyword) to one of 86 | - a function of op-map that returns a response map 87 | - a literal response map 88 | 89 | Notes: 90 | - you must instrument every op that will be invoked during a test 91 | - every op must be supported 92 | - See (keys (com.grzm.awyeah.client.api/ops )) 93 | - will validate request payloads passed to `invoke` by default 94 | - you can disable request validation with (com.grzm.awyeah.client.api/validate-requests client false) 95 | - will not validate response payloads" 96 | [{:keys [api ops]}] 97 | (let [service (service/service-description (name api))] 98 | (doto (-> (->Client {:service service 99 | :validate-requests? (atom true)} (atom {})) 100 | (assoc :api (-> service :metadata :cognitect.aws/service-name) 101 | :service service)) 102 | (instrument ops)))) 103 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/client/validation.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.client.validation 5 | "For internal use. Don't call directly." 6 | (:require 7 | [com.grzm.awyeah.client.protocol :as client.protocol] 8 | [com.grzm.awyeah.service :as service])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defn validate-requests? 13 | "For internal use. Don't call directly." 14 | [client] 15 | (some-> client client.protocol/-get-info :validate-requests? deref)) 16 | 17 | (def ^:private registry-ref (delay (requiring-resolve 'clojure.spec.alpha/registry))) 18 | (defn registry 19 | "For internal use. Don't call directly." 20 | [& args] (apply @registry-ref args)) 21 | 22 | (def ^:private valid?-ref (delay (requiring-resolve 'clojure.spec.alpha/valid?))) 23 | (defn valid? 24 | "For internal use. Don't call directly." 25 | [& args] (apply @valid?-ref args)) 26 | 27 | (def ^:private explain-data-ref (delay (requiring-resolve 'clojure.spec.alpha/explain-data))) 28 | (defn explain-data 29 | "For internal use. Don't call directly." 30 | [& args] (apply @explain-data-ref args)) 31 | 32 | (defn request-spec 33 | "For internal use. Don't call directly." 34 | [service op] 35 | (when-let [spec (service/request-spec-key service op)] 36 | (when (contains? (-> (registry) keys set) spec) 37 | spec))) 38 | 39 | (defn invalid-request-anomaly 40 | "For internal use. Don't call directly." 41 | [spec request] 42 | (assoc (explain-data spec request) 43 | :cognitect.anomalies/category :cognitect.anomalies/incorrect)) 44 | 45 | (defn unsupported-op-anomaly 46 | "For internal use. Don't call directly." 47 | [service op] 48 | {:cognitect.anomalies/category :cognitect.anomalies/unsupported 49 | :cognitect.anomalies/message "Operation not supported" 50 | :service (keyword (service/service-name service)) 51 | :op op}) 52 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/config.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.config 5 | (:require 6 | [clojure.string :as str])) 7 | 8 | (set! *warn-on-reflection* true) 9 | 10 | ;;; predicates 11 | 12 | (defn comment? [s] 13 | (str/starts-with? s "#")) 14 | 15 | (defn start-profile? [s] 16 | (str/starts-with? s "[")) 17 | 18 | (defn start-nested? [s] 19 | (re-find #"^[\w-_\.]+\s*=$" s)) 20 | 21 | (defn add-profile-kv? [s] 22 | (re-find #"^[\w-_\.]+\s*=.*\w+" s)) 23 | 24 | (defn add-nested-kv? [s] 25 | (re-find #"^\s+[\w-_\.]+\s*=.*\w+" s)) 26 | 27 | ;;; helpers 28 | 29 | (defn split-lines [s] 30 | (into [] 31 | (comp (map str/trimr) 32 | (remove str/blank?) 33 | (remove comment?)) 34 | (str/split-lines s))) 35 | 36 | (defn split-kv [s] 37 | (->> (str/split s #"=" 2) 38 | (map str/trim))) 39 | 40 | ;;; actions 41 | 42 | (defn set-profile-path [m line] 43 | (assoc m :path [:profiles (second (re-find #"\[(?:profile)?\s*(.+)\]" line))])) 44 | 45 | (defn ensure-profile-path [m] 46 | (update m :path (comp vec (partial take 2)))) 47 | 48 | (defn set-nested-path [m line] 49 | (update m :path #(conj (vec (take 2 %)) 50 | (-> line (str/replace #"=" "") (str/trim))))) 51 | 52 | (defn add-profile-kv [m line] 53 | (let [[k v] (split-kv line)] 54 | (update-in m (take 2 (:path m)) assoc k v))) 55 | 56 | (defn add-nested-kv [m line] 57 | (let [[k v] (split-kv line)] 58 | (update-in m (:path m) assoc k v))) 59 | 60 | ;;; main 61 | 62 | (defn parse 63 | "Return the profiles in the configuration file." 64 | [file] 65 | (->> file 66 | slurp 67 | split-lines 68 | (reduce (fn [m ^String line] 69 | (cond (start-profile? line) 70 | (set-profile-path m line) 71 | 72 | (start-nested? line) 73 | (set-nested-path m line) 74 | 75 | (add-profile-kv? line) 76 | (-> m 77 | (add-profile-kv line) 78 | (ensure-profile-path)) 79 | 80 | (add-nested-kv? line) 81 | (add-nested-kv m line) 82 | 83 | :else 84 | (throw (ex-info "Invalid format in config" {:file file})))) 85 | {:profiles {}}) 86 | :profiles)) 87 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/dynaload.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.dynaload) 5 | 6 | (set! *warn-on-reflection* true) 7 | 8 | (defonce ^:private dynalock (Object.)) 9 | 10 | (defn load-ns [ns] 11 | (locking dynalock 12 | (require (symbol ns)))) 13 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/ec2_metadata_utils.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.ec2-metadata-utils 5 | "Impl, don't call directly" 6 | (:require 7 | [clojure.core.async :as a] 8 | [clojure.string :as str] 9 | [com.grzm.awyeah.http :as http] 10 | [com.grzm.awyeah.json :as json] 11 | [com.grzm.awyeah.retry :as retry] 12 | [com.grzm.awyeah.util :as u]) 13 | (:import 14 | (java.net URI))) 15 | 16 | (set! *warn-on-reflection* true) 17 | 18 | (def ^:const ec2-metadata-service-override-system-property "com.amazonaws.sdk.ec2MetadataServiceEndpointOverride") 19 | (def ^:const dynamic-data-root "/latest/dynamic/") 20 | (def ^:const security-credentials-path "/latest/meta-data/iam/security-credentials/") 21 | (def ^:const instance-identity-document "instance-identity/document") 22 | 23 | ;; ECS 24 | (def ^:const container-credentials-relative-uri-env-var "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") 25 | (def ^:const container-credentials-full-uri-env-var "AWS_CONTAINER_CREDENTIALS_FULL_URI") 26 | (def ^:const container-authorization-token-env-var "AWS_CONTAINER_AUTHORIZATION_TOKEN") 27 | 28 | (def ^:const ec2-metadata-host "http://169.254.169.254") 29 | (def ^:const ecs-metadata-host "http://169.254.170.2") 30 | 31 | (defn in-container? [] 32 | (or (u/getenv container-credentials-relative-uri-env-var) 33 | (u/getenv container-credentials-full-uri-env-var))) 34 | 35 | (defn build-path [& components] 36 | (str/replace (str/join \/ components) #"\/\/+" (constantly "/"))) 37 | 38 | (defn- build-uri 39 | [host path] 40 | (str host "/" (cond-> path (str/starts-with? path "/") (subs 1)))) 41 | 42 | (defn get-host-address 43 | "Gets the EC2 (or ECS) metadata host address" 44 | [] 45 | (or (u/getProperty ec2-metadata-service-override-system-property) 46 | (when (in-container?) ecs-metadata-host) 47 | ec2-metadata-host)) 48 | 49 | (defn- request-map 50 | [^URI uri] 51 | (let [auth-token (u/getenv container-authorization-token-env-var)] 52 | {:scheme (.getScheme uri) 53 | :server-name (.getHost uri) 54 | :server-port (or (when (pos? (.getPort uri)) (.getPort uri)) 55 | (when (#{"https"} (.getScheme uri)) 443) 56 | 80) 57 | :uri (.getPath uri) 58 | :request-method :get 59 | :headers (cond-> {:accept "*/*"} 60 | auth-token 61 | (assoc "Authorization" auth-token))})) 62 | 63 | (defn get-data [uri http-client] 64 | (let [response (a/str (:body response))))) 72 | 73 | (defn get-data-at-path [path http-client] 74 | (get-data (build-uri (get-host-address) path) http-client)) 75 | 76 | (defn get-listing [uri http-client] 77 | (some-> (get-data uri http-client) str/split-lines)) 78 | 79 | (defn get-listing-at-path [path http-client] 80 | (get-listing (build-uri (get-host-address) path) http-client)) 81 | 82 | (defn get-ec2-instance-data [http-client] 83 | (some-> (build-path dynamic-data-root instance-identity-document) 84 | (get-data-at-path http-client) 85 | (json/read-str))) 86 | 87 | (defn get-ec2-instance-region 88 | [http-client] 89 | (:region (get-ec2-instance-data http-client))) 90 | 91 | (defn container-credentials [http-client] 92 | (let [endpoint (or (when-let [path (u/getenv container-credentials-relative-uri-env-var)] 93 | (str (get-host-address) path)) 94 | (u/getenv container-credentials-full-uri-env-var))] 95 | (some-> endpoint (get-data http-client) (json/read-str)))) 96 | 97 | (defn instance-credentials [http-client] 98 | (when (not (in-container?)) 99 | (when-let [cred-name (first (get-listing-at-path security-credentials-path http-client))] 100 | (some-> (get-data-at-path (str security-credentials-path cred-name) http-client) 101 | (json/read-str))))) 102 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/endpoint.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.endpoint 5 | "Impl, don't call directly." 6 | (:refer-clojure :exclude [resolve]) 7 | (:require 8 | [clojure.edn :as edn] 9 | [clojure.java.io :as io] 10 | [clojure.string :as str] 11 | [com.grzm.awyeah.service :as service])) 12 | 13 | (set! *warn-on-reflection* true) 14 | 15 | (defn descriptor-resource-path [] (format "%s/endpoints.edn" service/base-resource-path)) 16 | 17 | (defn read-endpoints-description [] 18 | (if-let [resource (io/resource (descriptor-resource-path))] 19 | (edn/read-string (slurp resource)) 20 | (throw (ex-info (str "Cannot find resource " (descriptor-resource-path) ".") {})))) 21 | 22 | (defn resolver 23 | "Create a new endpoint resolver." 24 | [] 25 | (read-endpoints-description)) 26 | 27 | (defn render-uri 28 | "Given a template, e.g. \"{a}.{b}.{c}\", and a map of replacements 29 | with keys matching those in the template, replaces {a} with the 30 | value bound to :a in replacements, then {b}, then {c}." 31 | [replacements template] 32 | (str/replace template 33 | #"\{([^}]+)\}" 34 | #(get replacements (second %)))) 35 | 36 | (defn service-resolve 37 | "Resolve the endpoint for the given service." 38 | [partition service-name service region-key] 39 | (let [endpoint (get-in service [:endpoints region-key]) 40 | region (name region-key) 41 | result (merge (:defaults partition) 42 | (:defaults service) 43 | endpoint 44 | {:partition (:partition partition) 45 | :region region 46 | :dnsSuffix (:dnsSuffix partition)}) 47 | uri-parts {"service" service-name 48 | "region" region 49 | "dnsSuffix" (:dnsSuffix partition)}] 50 | (cond-> result 51 | (:hostname result) 52 | (update :hostname (partial render-uri uri-parts)) 53 | 54 | (:sslCommonName result) 55 | (update :sslCommonName (partial render-uri uri-parts))))) 56 | 57 | (defn partition-resolve 58 | [{:keys [services] :as partition} service-key region-key] 59 | (when (contains? (-> partition :regions keys set) region-key) 60 | (let [{:keys [partitionEndpoint isRegionalized] :as service} (get services service-key) 61 | endpoint-key (if (and partitionEndpoint (not isRegionalized)) 62 | (keyword partitionEndpoint) 63 | region-key)] 64 | (service-resolve partition (name service-key) service endpoint-key)))) 65 | 66 | (defn resolve* 67 | "Resolves an endpoint for a given service and region. 68 | 69 | service keyword Identify a AWS service (e.g. :s3) 70 | region keyword Identify a AWS region (e.g. :us-east-1). 71 | 72 | Return a map with the following keys: 73 | 74 | :partition The name of the partition. 75 | :region The region of the endpoint. 76 | :hostname The hostname to use. 77 | :sslCommonName The sslCommonName to use (optional). 78 | :credentialScope The Signature v4 credential scope (optional). 79 | :signatureVersions A list of possible signature versions (optional). 80 | :protocols A list of supported protocols." 81 | [service-key region] 82 | (some #(partition-resolve % service-key region) 83 | (:partitions (resolver)))) 84 | 85 | (def resolve (memoize resolve*)) 86 | 87 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 88 | 89 | (defprotocol EndpointProvider 90 | (-fetch [provider region])) 91 | 92 | (defn default-endpoint-provider [endpointPrefix endpoint-override] 93 | (reify EndpointProvider 94 | (-fetch [_ region] 95 | (if-let [ep (resolve (keyword endpointPrefix) (keyword region))] 96 | (merge ep (if (string? endpoint-override) 97 | {:hostname endpoint-override} 98 | endpoint-override)) 99 | {:cognitect.anomalies/category :cognitect.anomalies/fault 100 | :cognitect.anomalies/message "No known endpoint."})))) 101 | 102 | (defn fetch [provider region] 103 | (-fetch provider region)) 104 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/http.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.http 5 | "Impl, don't call directly." 6 | (:require 7 | [clojure.core.async :as a] 8 | [clojure.edn :as edn] 9 | [clojure.java.io :as io])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defprotocol HttpClient 14 | (-submit [_ request channel] 15 | "Submit an http request, channel will be filled with response. Returns ch. 16 | 17 | Request map: 18 | 19 | :scheme :http or :https 20 | :server-name string 21 | :server-port integer 22 | :uri string 23 | :query-string string, optional 24 | :request-method :get/:post/:put/:head/:delete 25 | :headers map from downcased string to string 26 | :body ByteBuffer, optional 27 | :timeout-msec opt, total request send/receive timeout 28 | :meta opt, data to be added to the response map 29 | 30 | content-type must be specified in the headers map 31 | content-length is derived from the ByteBuffer passed to body 32 | 33 | Response map: 34 | 35 | :status integer HTTP status code 36 | :body ByteBuffer, optional 37 | :headers map from downcased string to string 38 | :meta opt, data from the request 39 | 40 | On error, response map is per cognitect.anomalies. 41 | 42 | Alpha. This will absolutely change.") 43 | (-stop [_] "Stops the client, releasing resources")) 44 | 45 | (defn submit 46 | ([client request] 47 | (-submit client request (a/chan 1))) 48 | ([client request channel] 49 | (-submit client request channel))) 50 | 51 | (defn stop 52 | "Stops the client, releasing resources. 53 | 54 | Alpha. Subject to change." 55 | [client] 56 | (-stop client)) 57 | 58 | (defn client? 59 | [c] 60 | (satisfies? HttpClient c)) 61 | 62 | (defn read-config 63 | [url] 64 | (-> url slurp edn/read-string)) 65 | 66 | ;; TODO consider providing config arguments to http constructor 67 | (defn- configured-client 68 | "If a single com_grzm_awyeah_http.edn is found on the classpath, 69 | returns the symbol bound to :constructor-var. 70 | 71 | Throws if 0 or > 1 com_grzm_awyeah_http.edn files are found. 72 | " 73 | [] 74 | (try 75 | (-> (io/resource "com_grzm_awyeah_http.edn") read-config :constructor-var) 76 | (catch Throwable _ 77 | (throw (RuntimeException. "Could not find com_grzm_awyeah_http.edn on classpath."))))) 78 | 79 | (defn resolve-http-client 80 | [http-client-or-sym] 81 | (let [c (or (when (symbol? http-client-or-sym) 82 | (let [ctor (requiring-resolve http-client-or-sym)] 83 | (ctor))) 84 | http-client-or-sym 85 | (let [ctor (requiring-resolve (configured-client))] 86 | (ctor)))] 87 | (when-not (client? c) 88 | (throw (ex-info "not an http client" {:provided http-client-or-sym 89 | :resolved c}))) 90 | c)) 91 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/http/awyeah.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.http.awyeah 2 | (:require 3 | [com.grzm.awyeah.http :as aws] 4 | [com.grzm.awyeah.http-client :as impl])) 5 | 6 | (set! *warn-on-reflection* true) 7 | 8 | (defn create 9 | [] 10 | (let [c (impl/create nil)] 11 | (reify aws/HttpClient 12 | (-submit [_ request channel] 13 | (impl/submit c request channel)) 14 | (-stop [_] 15 | (impl/stop c))))) 16 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/http_client/client.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Michael Glaesemann 2 | ;; Heavily inspired by congitect.http-client, Copyright (c) Cognitect, Inc. 3 | ;; All rights reserved. 4 | 5 | (ns com.grzm.awyeah.http-client.client) 6 | 7 | (defprotocol Client 8 | (-submit [_ request ch])) 9 | 10 | (defn submit [client request ch] 11 | (-submit client request ch)) 12 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/http_client/specs.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Michael Glaesemann 2 | ;; Heavily inspired by congitect.http-client, Copyright (c) Cognitect, Inc. 3 | ;; All rights reserved. 4 | 5 | (ns com.grzm.awyeah.http-client.specs 6 | (:require 7 | [clojure.spec.alpha :as s]) 8 | (:import 9 | (java.nio ByteBuffer))) 10 | 11 | (defn- keyword-or-non-empty-string? [x] 12 | (or (keyword? x) 13 | (and (string? x) (not-empty x)))) 14 | 15 | (s/def :com.grzm.awyeah.http-client/server-name string?) 16 | (s/def :com.grzm.awyeah.http-client/server-port int?) 17 | (s/def :com.grzm.awyeah.http-client/uri string?) 18 | (s/def :com.grzm.awyeah.http-client/request-method keyword?) 19 | (s/def :com.grzm.awyeah.http-client/scheme keyword-or-non-empty-string?) 20 | (s/def :com.grzm.awyeah.http-client/timeout-msec int?) 21 | (s/def :com.grzm.awyeah.http-client/meta map?) 22 | (s/def :com.grzm.awyeah.http-client/body (s/nilable #(instance? ByteBuffer %))) 23 | (s/def :com.grzm.awyeah.http-client/query-string string?) 24 | (s/def :com.grzm.awyeah.http-client/headers map?) 25 | 26 | (s/def :com.grzm.awyeah.http-client/submit-request 27 | (s/keys :req-un [:com.grzm.awyeah.http-client/server-name 28 | :com.grzm.awyeah.http-client/server-port 29 | :com.grzm.awyeah.http-client/uri 30 | :com.grzm.awyeah.http-client/request-method 31 | :com.grzm.awyeah.http-client/scheme] 32 | :opt [:com.grzm.awyeah.http-client/timeout-msec 33 | :com.grzm.awyeah.http-client/meta] 34 | :opt-un [:com.grzm.awyeah.http-client/body 35 | :com.grzm.awyeah.http-client/query-string 36 | :com.grzm.awyeah.http-client/headers])) 37 | 38 | (s/def :com.grzm.awyeah.http-client/status int?) 39 | 40 | (s/def :com.grzm.awyeah.http-client/submit-http-response 41 | (s/keys :req-un [:com.grzm.awyeah.http-client/status] 42 | :opt [:com.grzm.awyeah.http-client/meta] 43 | :opt-un [:com.grzm.awyeah.http-client/body 44 | :com.grzm.awyeah.http-client/headers])) 45 | 46 | (s/def :com.grzm.awyeah.http-client/error keyword?) 47 | (s/def :com.grzm.awyeah.http-client/throwable #(instance? Throwable %)) 48 | 49 | (s/def :com.grzm.awyeah.http-client/submit-error-response 50 | (s/keys :req [:com.grzm.awyeah.http-client/error] 51 | :opt [:com.grzm.awyeah.http-client/throwable 52 | :com.grzm.awyeah.http-client/meta])) 53 | 54 | (s/def :com.grzm.awyeah.http-client/submit-response 55 | (s/or :http-response :com.grzm.awyeah.http-client/submit-http-response 56 | :error-response :com.grzm.awyeah.http-client/submit-error-response)) 57 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/interceptors.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.interceptors 5 | "Impl, don't call directly." 6 | (:require 7 | [com.grzm.awyeah.service :as service] 8 | [com.grzm.awyeah.util :as util])) 9 | 10 | (set! *warn-on-reflection* true) 11 | 12 | (defmulti modify-http-request (fn [service _op-map _http-request] 13 | (service/service-name service))) 14 | 15 | (defmethod modify-http-request :default [_service _op-map http-request] http-request) 16 | 17 | (def md5-blacklist 18 | "Set of ops that should not get the Content-MD5 header. 19 | 20 | See https://github.com/aws/aws-sdk-java-v2/blob/master/services/s3/src/main/java/software/amazon/awssdk/services/s3/internal/handlers/AddContentMd5HeaderInterceptor.java " 21 | #{:PutObject :UploadPart}) 22 | 23 | (defmethod modify-http-request "s3" [service op-map http-request] 24 | (if (and (= "md5" (get-in service [:metadata :checksumFormat])) 25 | (not (md5-blacklist (:op op-map))) 26 | (:body http-request)) 27 | (update http-request :headers assoc "Content-MD5" (-> http-request :body util/md5 util/base64-encode)) 28 | http-request)) 29 | 30 | (defmethod modify-http-request "apigatewaymanagementapi" [_service op-map http-request] 31 | (if (= :PostToConnection (:op op-map)) 32 | (update http-request :uri str (-> op-map :request :ConnectionId)) 33 | http-request)) 34 | 35 | ;; See https://github.com/aws/aws-sdk-java-v2/blob/985ec92c0dfac868b33791fe4623296c68e2feab/services/glacier/src/main/java/software/amazon/awssdk/services/glacier/internal/GlacierExecutionInterceptor.java#L40 36 | (defmethod modify-http-request "glacier" [service _op-map http-request] 37 | (assoc-in http-request 38 | [:headers "x-amz-glacier-version"] 39 | (get-in service [:metadata :apiVersion]))) 40 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/json.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.json 2 | (:require [cheshire.core :as json])) 3 | 4 | (defn write-str [x] 5 | (json/generate-string x)) 6 | 7 | (defn read-str [s] 8 | (json/parse-string s keyword)) 9 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/protocols.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.protocols 5 | "Impl, don't call directly. " 6 | (:require 7 | [clojure.string :as str] 8 | [com.grzm.awyeah.json :as json] 9 | [com.grzm.awyeah.util :as util]) 10 | (:import 11 | (java.util Date))) 12 | 13 | (set! *warn-on-reflection* true) 14 | 15 | (defmulti parse-http-response 16 | "HTTP response -> AWS response" 17 | (fn [service _op-map _http-response] 18 | (get-in service [:metadata :protocol]))) 19 | 20 | (defmulti build-http-request 21 | "AWS request -> HTTP request." 22 | (fn [service _op-map] 23 | (get-in service [:metadata :protocol]))) 24 | 25 | (defn ^:private status-code->anomaly-category [^long code] 26 | (case code 27 | 304 :cognitect.anomalies/conflict 28 | 403 :cognitect.anomalies/forbidden 29 | 404 :cognitect.anomalies/not-found 30 | 429 :cognitect.anomalies/busy 31 | 503 :cognitect.anomalies/busy 32 | 504 :cognitect.anomalies/unavailable 33 | (if (<= 300 code 499) 34 | :cognitect.anomalies/incorrect 35 | :cognitect.anomalies/fault))) 36 | 37 | (defn sanitize-error-code 38 | "Per https://smithy.io/2.0/aws/protocols/aws-restjson1-protocol.html#operation-error-serialization: 39 | If a : character is present, then take only the contents before the first : character in the value. 40 | If a # character is present, then take only the contents after the first # character in the value." 41 | [error-code] 42 | (some-> error-code 43 | (str/split #":") 44 | first 45 | (str/split #"#" 2) 46 | last)) 47 | 48 | (defn error-code 49 | "Attempt to extract an error code from well known locations in an 50 | error response body. Returns nil if none are found. 51 | 52 | See: 53 | https://smithy.io/2.0/aws/protocols/aws-restjson1-protocol.html#operation-error-serialization 54 | https://smithy.io/2.0/aws/protocols/aws-json-1_0-protocol.html#operation-error-serialization 55 | https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html#operation-error-serialization 56 | https://smithy.io/2.0/aws/protocols/aws-restxml-protocol.html#error-response-serialization 57 | https://smithy.io/2.0/aws/protocols/aws-query-protocol.html#operation-error-serialization 58 | https://smithy.io/2.0/aws/protocols/aws-ec2-query-protocol.html#operation-error-serialization" 59 | [http-response] 60 | (or (-> http-response :headers (get "x-amzn-errortype")) 61 | (-> http-response :body :__type) 62 | (-> http-response :body :code) 63 | (-> http-response :body :Error :Code) 64 | (-> http-response :body :ErrorResponse :Error :Code) 65 | (-> http-response :body :Response :Errors :Error :Code))) 66 | 67 | (defn ^:private error-code->anomaly-category 68 | "Given an error message extracted from an error response body *that we 69 | understand*, returns the appropriate anomaly category, or nil if none 70 | are found." 71 | [error-code] 72 | (condp = error-code 73 | "ThrottlingException" :cognitect.anomalies/busy 74 | nil)) 75 | 76 | (defn ^:private anomaly-category 77 | "Given an http-response with the body already coerced to a Clojure map, 78 | attempt to return an anomaly-category for a specific error message or 79 | status. Returns nil if none are found." 80 | [status sanitized-error-code] 81 | (or (error-code->anomaly-category sanitized-error-code) 82 | (status-code->anomaly-category status))) 83 | 84 | (defn ^:private anomaly-message 85 | "Given 301 with an x-amz-bucket-region header, returns a clear message with direction 86 | for the user to resubmit the request to the correct region. Else returns nil." 87 | [response-map] 88 | (when-let [region (and (= 301 (:status response-map)) 89 | (get (:headers response-map) "x-amz-bucket-region"))] 90 | (str "The bucket is in this region: " region ". Please use this region to retry the request."))) 91 | 92 | (defn headers [service operation] 93 | (let [{:keys [protocol targetPrefix jsonVersion]} (:metadata service)] 94 | (cond-> {"x-amz-date" (util/format-date util/x-amz-date-format (Date.))} 95 | (contains? #{"json" "rest-json"} protocol) 96 | (assoc "x-amz-target" (str targetPrefix "." (:name operation)) 97 | "content-type" (str "application/x-amz-json-" jsonVersion) 98 | ;; NOTE: apigateway returns application/hal+json unless 99 | ;; we specify the accept header 100 | "accept" "application/json") 101 | (contains? #{"query" "ec2"} protocol) 102 | (assoc "content-type" "application/x-www-form-urlencoded; charset=utf-8")))) 103 | 104 | (defn ^:private parse-encoded-string 105 | "Given non-nil String, determine the encoding (currently either XML or JSON). Return a Map 106 | representation of the encoded data. 107 | 108 | Returns nil if encoded-str is nil." 109 | [encoded-str] 110 | (when (seq encoded-str) 111 | (if (= \< (first encoded-str)) 112 | (-> encoded-str util/xml-read util/xml->map) 113 | (-> encoded-str (json/read-str))))) 114 | 115 | (defn parse-http-error-response 116 | "Given an http error response (any status code 300 or above), return an aws-api-specific response 117 | Map." 118 | [{:keys [status] :as http-response}] 119 | (let [http-response* (update http-response :body #(some-> % util/bbuf->str parse-encoded-string)) 120 | sanitized-error-code (-> http-response* error-code sanitize-error-code) 121 | category (anomaly-category (:status http-response) sanitized-error-code) 122 | message (anomaly-message http-response*)] 123 | (with-meta 124 | (cond-> (assoc (:body http-response*) :cognitect.aws.http/status status) 125 | category (assoc :cognitect.anomalies/category category) 126 | message (assoc :cognitect.anomalies/message message) 127 | sanitized-error-code (assoc :cognitect.aws.error/code sanitized-error-code)) 128 | http-response))) 129 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/protocols/ec2.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.protocols.ec2 5 | "Impl, don't call directly." 6 | (:require 7 | [com.grzm.awyeah.protocols :as aws.protocols] 8 | [com.grzm.awyeah.protocols.query :as query] 9 | [com.grzm.awyeah.shape :as shape] 10 | [com.grzm.awyeah.util :as util])) 11 | 12 | (set! *warn-on-reflection* true) 13 | 14 | (defn serialized-name 15 | [shape default] 16 | (or (:queryName shape) 17 | (when-let [name (:locationName shape)] 18 | (apply str (Character/toUpperCase ^Character (first name)) (rest name))) 19 | default)) 20 | 21 | (defmulti serialize 22 | (fn [_shapes shape _args _serialized _prefix] (:type shape))) 23 | 24 | (defmethod serialize :default 25 | [shapes shape args serialized prefix] 26 | (query/serialize shapes shape args serialized prefix)) 27 | 28 | (defmethod serialize "structure" 29 | [shapes shape args serialized prefix] 30 | (let [args (util/with-defaults shape args)] 31 | (reduce (fn [serialized k] 32 | (let [member-shape (shape/resolve shapes (shape/structure-member-shape-ref shape k)) 33 | member-name (serialized-name member-shape (name k))] 34 | (if (contains? args k) 35 | (serialize shapes member-shape (k args) serialized (conj prefix member-name)) 36 | serialized))) 37 | serialized 38 | (keys (:members shape))))) 39 | 40 | (defmethod serialize "list" 41 | [shapes shape args serialized prefix] 42 | (let [member-shape (shape/resolve shapes (shape/list-member-shape-ref shape))] 43 | (reduce (fn [serialized [i member]] 44 | (serialize shapes member-shape member serialized (conj prefix (str i)))) 45 | serialized 46 | (map-indexed (fn [i member] [(inc i) member]) args)))) 47 | 48 | (defmethod aws.protocols/build-http-request "ec2" 49 | [service op-map] 50 | (query/build-query-http-request op-map service serialize)) 51 | 52 | (defmethod aws.protocols/parse-http-response "ec2" 53 | [service op-map http-response] 54 | (query/build-query-http-response service op-map http-response)) 55 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/protocols/json.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.protocols.json 5 | "Impl, don't call directly." 6 | (:require 7 | [com.grzm.awyeah.protocols :as aws.protocols] 8 | [com.grzm.awyeah.shape :as shape] 9 | [com.grzm.awyeah.util :as util])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defmulti serialize 14 | (fn [_shapes shape _data] (:type shape))) 15 | 16 | (defmethod serialize :default 17 | [shapes shape data] 18 | (shape/json-serialize shapes shape data)) 19 | 20 | (defmethod serialize "structure" 21 | [shapes shape data] 22 | (->> (util/with-defaults shape data) 23 | (shape/json-serialize shapes shape))) 24 | 25 | (defmethod aws.protocols/build-http-request "json" 26 | [service {:keys [op request]}] 27 | (let [operation (get-in service [:operations op]) 28 | shapes (:shapes service) 29 | input-shape (shape/resolve shapes (:input operation))] 30 | {:request-method :post 31 | :scheme :https 32 | :server-port 443 33 | :uri "/" 34 | :headers (aws.protocols/headers service operation) 35 | :body (serialize shapes input-shape (or request {}))})) 36 | 37 | (defmethod aws.protocols/parse-http-response "json" 38 | [service {:keys [op]} {:keys [body]}] 39 | (let [operation (get-in service [:operations op]) 40 | shapes (:shapes service) 41 | output-shape (shape/resolve shapes (:output operation)) 42 | body-str (util/bbuf->str body)] 43 | (if output-shape 44 | (shape/json-parse shapes output-shape body-str) 45 | {}))) 46 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/protocols/query.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.protocols.query 5 | "Impl, don't call directly." 6 | (:require 7 | [clojure.string :as str] 8 | [com.grzm.awyeah.protocols :as aws.protocols] 9 | [com.grzm.awyeah.shape :as shape] 10 | [com.grzm.awyeah.util :as util])) 11 | 12 | (set! *warn-on-reflection* true) 13 | 14 | ;; ---------------------------------------------------------------------------------------- 15 | ;; Serializer 16 | ;; ---------------------------------------------------------------------------------------- 17 | 18 | (defn serialized-name 19 | [shape default] 20 | (or (:locationName shape) 21 | default)) 22 | 23 | (defn prefix-assoc 24 | [serialized prefix val] 25 | (assoc serialized (str/join "." prefix) val)) 26 | 27 | (defmulti serialize 28 | (fn [_shapes shape _args _serialized _prefix] (:type shape))) 29 | 30 | (defmethod serialize :default 31 | [_shapes _shape args serialized prefix] 32 | (prefix-assoc serialized prefix (str args))) 33 | 34 | (defmethod serialize "structure" 35 | [shapes shape args serialized prefix] 36 | (let [args (util/with-defaults shape args)] 37 | (reduce (fn [serialized k] 38 | (let [member-shape (shape/resolve shapes (shape/structure-member-shape-ref shape k)) 39 | member-name (serialized-name member-shape (name k))] 40 | (if (contains? args k) 41 | (serialize shapes member-shape (k args) serialized (conj prefix member-name)) 42 | serialized))) 43 | serialized 44 | (keys (:members shape))))) 45 | 46 | (defmethod serialize "list" 47 | [shapes shape args serialized prefix] 48 | (if (empty? args) 49 | (prefix-assoc serialized prefix "") 50 | (let [member-shape (shape/resolve shapes (shape/list-member-shape-ref shape)) 51 | list-prefix (if (:flattened shape) 52 | (conj (vec (butlast prefix)) (serialized-name member-shape (last prefix))) 53 | (conj prefix (serialized-name member-shape "member")))] 54 | (reduce (fn [serialized [i member]] 55 | (serialize shapes member-shape member serialized (conj list-prefix (str i)))) 56 | serialized 57 | (map-indexed (fn [i member] [(inc i) member]) args))))) 58 | 59 | (defmethod serialize "map" 60 | [shapes shape args serialized prefix] 61 | (let [map-prefix (if (:flattened shape) prefix (conj prefix "entry")) 62 | key-shape (shape/resolve shapes (shape/map-key-shape-ref shape)) 63 | key-suffix (serialized-name key-shape "key") 64 | value-shape (shape/resolve shapes (shape/map-value-shape-ref shape)) 65 | value-suffix (serialized-name value-shape "value")] 66 | (reduce (fn [serialized [i k v]] 67 | (as-> serialized $ 68 | (serialize shapes key-shape (name k) $ (conj map-prefix (str i) key-suffix)) 69 | (serialize shapes value-shape v $ (conj map-prefix (str i) value-suffix)))) 70 | serialized 71 | (map-indexed (fn [i [k v]] [(inc i) k v]) args)))) 72 | 73 | (defmethod serialize "blob" 74 | [_shapes _shape args serialized prefix] 75 | (prefix-assoc serialized prefix (util/base64-encode args))) 76 | 77 | (defmethod serialize "timestamp" 78 | [_shapes shape args serialized prefix] 79 | (prefix-assoc serialized prefix (shape/format-date shape 80 | args 81 | (partial util/format-date util/iso8601-date-format)))) 82 | 83 | (defmethod serialize "boolean" 84 | [_shapes _shape args serialized prefix] 85 | (prefix-assoc serialized prefix (if args "true" "false"))) 86 | 87 | (defn build-query-http-request 88 | [{:keys [op request]} service serialize] 89 | (let [operation (get-in service [:operations op]) 90 | shapes (:shapes service) 91 | input-shape (shape/resolve shapes (:input operation)) 92 | params {"Action" (name op) 93 | "Version" (get-in service [:metadata :apiVersion])}] 94 | {:request-method :post 95 | :scheme :https 96 | :server-port 443 97 | :uri "/" 98 | :headers (aws.protocols/headers service operation) 99 | :body (util/query-string 100 | (serialize shapes input-shape request params []))})) 101 | 102 | (defmethod aws.protocols/build-http-request "query" 103 | [service op-map] 104 | (build-query-http-request op-map service serialize)) 105 | 106 | (defn build-query-http-response 107 | [service {:keys [op]} {:keys [body]}] 108 | (let [operation (get-in service [:operations op]) 109 | shapes (:shapes service)] 110 | (if-let [output-shape (shape/resolve shapes (:output operation))] 111 | (shape/xml-parse shapes output-shape (util/bbuf->str body)) 112 | (util/xml->map (util/xml-read (util/bbuf->str body)))))) 113 | 114 | (defmethod aws.protocols/parse-http-response "query" 115 | [service op-map http-response] 116 | (build-query-http-response service op-map http-response)) 117 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/protocols/rest_json.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.protocols.rest-json 5 | "Impl, don't call directly." 6 | (:require 7 | [com.grzm.awyeah.protocols :as aws.protocols] 8 | [com.grzm.awyeah.protocols.rest :as rest] 9 | [com.grzm.awyeah.shape :as shape] 10 | [com.grzm.awyeah.util :as util])) 11 | 12 | (set! *warn-on-reflection* true) 13 | 14 | (defmulti serialize 15 | "json body args serializer passed to rest/build-http-request 16 | 17 | Obs: this fn doesn't use the 2nd arg, but the one in rest-xml 18 | does, and this function gets invoked by rest/build-http-request, 19 | which requires a 3 arg serialize fn." 20 | (fn [_shapes _ shape _data] (:type shape))) 21 | 22 | (defmethod serialize :default 23 | [shapes _ shape data] 24 | (shape/json-serialize shapes shape data)) 25 | 26 | (defmethod serialize "structure" 27 | [shapes _ shape data] 28 | (some->> (util/with-defaults shape data) 29 | not-empty 30 | (shape/json-serialize shapes shape))) 31 | 32 | (defmethod serialize "timestamp" 33 | [_shapes _ shape data] 34 | (shape/format-date shape data)) 35 | 36 | (defmethod aws.protocols/build-http-request "rest-json" 37 | [service op-map] 38 | (rest/build-http-request service op-map serialize)) 39 | 40 | (defmethod aws.protocols/parse-http-response "rest-json" 41 | [service op-map http-response] 42 | (rest/parse-http-response service 43 | op-map 44 | http-response 45 | shape/json-parse)) 46 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/protocols/rest_xml.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.protocols.rest-xml 5 | "Impl, don't call directly." 6 | (:require 7 | [com.grzm.awyeah.protocols :as aws.protocols] 8 | [com.grzm.awyeah.protocols.rest :as rest] 9 | [com.grzm.awyeah.shape :as shape])) 10 | 11 | (set! *warn-on-reflection* true) 12 | 13 | (defn serialize 14 | "xml body args serializer passed to rest/build-http-request" 15 | [shapes shape-name shape data] 16 | (when data 17 | (shape/xml-serialize shapes 18 | shape 19 | data 20 | (or (:locationName shape) shape-name)))) 21 | 22 | (defmethod aws.protocols/build-http-request "rest-xml" 23 | [service op-map] 24 | (rest/build-http-request service op-map serialize)) 25 | 26 | (defmethod aws.protocols/parse-http-response "rest-xml" 27 | [service op-map http-response] 28 | (rest/parse-http-response service 29 | op-map 30 | http-response 31 | shape/xml-parse)) 32 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/region.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.region 5 | "Region providers. Primarily for internal use, and subject to change." 6 | (:require 7 | [clojure.java.io :as io] 8 | [clojure.string :as str] 9 | [clojure.tools.logging :as log] 10 | [com.grzm.awyeah.config :as config] 11 | [com.grzm.awyeah.ec2-metadata-utils :as ec2] 12 | [com.grzm.awyeah.util :as u]) 13 | (:import 14 | (java.io File))) 15 | 16 | (set! *warn-on-reflection* true) 17 | 18 | (defn ^:skip-wiki valid-region 19 | "For internal use. Don't call directly. 20 | 21 | Return the credential region if valid, otherwise nil." 22 | [region] 23 | ;; TODO: (dchelimsky 2018-07-27) maybe validate this against known regions? 24 | (when-not (str/blank? region) region)) 25 | 26 | (defprotocol RegionProvider 27 | (fetch [_] "Returns the region found by this provider, or nil.")) 28 | 29 | (defn chain-region-provider 30 | "Chain together multiple region providers. 31 | 32 | `fetch` calls each provider in order until one returns a non-nil result, 33 | or returns nil. 34 | 35 | Alpha. Subject to change." 36 | [providers] 37 | (reify RegionProvider 38 | (fetch [_] 39 | (or (valid-region (some fetch providers)) 40 | (throw (ex-info "No region found by any region provider." 41 | {:providers (map class providers)})))))) 42 | 43 | (defn environment-region-provider 44 | "Returns the region from the AWS_REGION env var, or nil if not present. 45 | 46 | Alpha. Subject to change." 47 | [] 48 | (reify RegionProvider 49 | (fetch [_] (valid-region (u/getenv "AWS_REGION"))))) 50 | 51 | (defn system-property-region-provider 52 | "Returns the region from the aws.region system property, or nil if not present. 53 | 54 | Alpha. Subject to change." 55 | [] 56 | (reify RegionProvider 57 | (fetch [_] (valid-region (u/getProperty "aws.region"))))) 58 | 59 | (defn profile-region-provider 60 | "Returns the region from an AWS configuration profile. 61 | 62 | Arguments: 63 | 64 | f File The profile configuration file. (default: ~/.aws/config) 65 | profile-name string The name of the profile in the file. (default: default) 66 | 67 | Parsed properties: 68 | 69 | region required 70 | 71 | Alpha. Subject to change." 72 | ([] 73 | (profile-region-provider (or (u/getenv "AWS_PROFILE") 74 | (u/getProperty "aws.profile") 75 | "default"))) 76 | ([profile-name] 77 | (profile-region-provider profile-name (or (io/file (u/getenv "AWS_CONFIG_FILE")) 78 | (io/file (u/getProperty "user.home") ".aws" "config")))) 79 | ([profile-name ^File f] 80 | (reify RegionProvider 81 | (fetch [_] 82 | (when (.exists f) 83 | (try 84 | (let [profile (get (config/parse f) profile-name)] 85 | (valid-region (get profile "region"))) 86 | (catch Throwable t 87 | (log/error t "Unable to fetch region from the AWS config file " (str f))))))))) 88 | 89 | (defn instance-region-provider 90 | "Returns the region from the ec2 instance's metadata service, 91 | or nil if the service can not be found. 92 | 93 | Alpha. Subject to change." 94 | [http-client] 95 | (let [cached-region (atom nil)] 96 | (reify RegionProvider 97 | (fetch [_] 98 | (or @cached-region 99 | (reset! cached-region (valid-region (ec2/get-ec2-instance-region http-client)))))))) 100 | 101 | (defn default-region-provider 102 | "Returns a chain-region-provider with, in order: 103 | 104 | environment-region-provider 105 | system-property-region-provider 106 | profile-region-provider 107 | instance-region-provider 108 | 109 | Alpha. Subject to change." 110 | [http-client] 111 | (chain-region-provider 112 | [(environment-region-provider) 113 | (system-property-region-provider) 114 | (profile-region-provider) 115 | (instance-region-provider http-client)])) 116 | 117 | (defn fetch-async 118 | "Returns a channel that will produce the result of calling fetch on 119 | the provider. 120 | 121 | Alpha. Subject to change." 122 | [provider] 123 | (u/fetch-async fetch provider "region")) 124 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/retry.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.retry 5 | (:require 6 | [clojure.core.async :as a])) 7 | 8 | (set! *warn-on-reflection* true) 9 | 10 | (defn ^:skip-wiki with-retry 11 | "For internal use. Do not call directly. 12 | 13 | Calls req-fn, a *non-blocking* function that wraps some operation 14 | and returns a channel. When the response to req-fn is retriable? 15 | and backoff returns an int, waits backoff ms and retries, otherwise 16 | puts response on resp-chan." 17 | [req-fn resp-chan retriable? backoff] 18 | (a/go-loop [retries 0] 19 | (let [resp (a/! resp-chan resp)) 26 | (a/>! resp-chan resp)))) 27 | resp-chan) 28 | 29 | (defn capped-exponential-backoff 30 | "Returns a function of the num-retries (so far), which returns the 31 | lesser of max-backoff and an exponentially increasing multiple of 32 | base, or nil when (>= num-retries max-retries). 33 | See with-retry to see how it is used. 34 | 35 | Alpha. Subject to change." 36 | [base max-backoff max-retries] 37 | (fn [num-retries] 38 | (when (< num-retries max-retries) 39 | (min max-backoff 40 | (* base (bit-shift-left 1 num-retries)))))) 41 | 42 | (def default-backoff 43 | "Returns (capped-exponential-backoff 100 20000 3). 44 | 45 | Alpha. Subject to change." 46 | (capped-exponential-backoff 100 20000 3)) 47 | 48 | (def default-retriable? 49 | "A fn of an http-response map which returns a truthy value 50 | if (:cognitect.anomalies/category http-response) is any of: 51 | - :cognitect.anomalies/busy 52 | - :cognitect.anomalies/interrupted 53 | - :cognitect.anomalies/unavailable 54 | 55 | Alpha. Subject to change." 56 | (comp #{:cognitect.anomalies/busy 57 | :cognitect.anomalies/interrupted 58 | :cognitect.anomalies/unavailable} 59 | :cognitect.anomalies/category)) 60 | -------------------------------------------------------------------------------- /src/com/grzm/awyeah/service.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns ^:skip-wiki com.grzm.awyeah.service 5 | "Impl, don't call directly." 6 | (:require 7 | [clojure.edn :as edn] 8 | [clojure.java.io :as io] 9 | [clojure.string :as str] 10 | [clojure.walk :as walk])) 11 | 12 | (set! *warn-on-reflection* true) 13 | 14 | (def base-ns "cognitect.aws") 15 | 16 | (def base-resource-path "cognitect/aws") 17 | 18 | (defn descriptor-resource-path [service-name] 19 | (str base-resource-path "/" service-name "/service.edn")) 20 | 21 | (defn descriptor-resource [service-name] 22 | (io/resource (descriptor-resource-path service-name))) 23 | 24 | (defn read-service-description 25 | "Return service description readerable source (anything supported by 26 | clojure.java.io/reader)." 27 | [readerable] 28 | (edn/read-string (slurp readerable))) 29 | 30 | (defn service-description [service-name] 31 | (if-let [resource (descriptor-resource service-name)] 32 | (read-service-description resource) 33 | (throw (ex-info (str "Cannot find resource " (descriptor-resource-path service-name) ".") {})))) 34 | 35 | (defn endpoint-prefix 36 | [service] 37 | (get-in service [:metadata :endpointPrefix])) 38 | 39 | (defn signing-name 40 | [service] 41 | (get-in service [:metadata :signingName])) 42 | 43 | (defn service-name [service] 44 | (-> service :metadata :uid 45 | (str/replace #"-\d{4}-\d{2}-\d{2}" "") 46 | (str/replace #"\s" "-") 47 | (str/replace #"\." "-"))) 48 | 49 | (defn ns-prefix 50 | "Returns the namespace prefix to use when looking up resources." 51 | [service] 52 | (format "%s.%s" base-ns (service-name service))) 53 | 54 | (defn spec-ns 55 | "The namespace for specs for service." 56 | [service] 57 | (symbol (format "%s.specs" (ns-prefix service)))) 58 | 59 | (defn load-specs [service] 60 | (require (spec-ns service))) 61 | 62 | (defonce svc-docs (atom {})) 63 | 64 | (defn with-ref-meta [m op doc] 65 | (let [ref-atom (atom nil) 66 | refs (:refs doc) 67 | updated-doc (walk/postwalk 68 | (fn [n] 69 | (if (contains? refs n) 70 | (with-meta n 71 | {'clojure.core.protocols/datafy #(-> ref-atom deref %)}) 72 | n)) 73 | doc)] 74 | (reset! ref-atom (:refs updated-doc)) 75 | (assoc m op (into {:name (name op)} updated-doc)))) 76 | 77 | (defn docs 78 | "Returns the docs for this service" 79 | [service] 80 | (let [k (service-name service)] 81 | (if-let [doc (get @svc-docs k)] 82 | doc 83 | (-> (swap! svc-docs 84 | assoc 85 | k 86 | (reduce-kv with-ref-meta 87 | {} 88 | (clojure.edn/read-string 89 | (slurp 90 | (io/resource (format "%s/%s/docs.edn" base-resource-path (service-name service))))))) 91 | (get k))))) 92 | 93 | (defn request-spec-key 94 | "Returns the key to look up in the spec registry for the spec for 95 | the request body of op." 96 | [service op] 97 | (load-specs service) 98 | (when-let [shape-key (some->> service :operations op :input :shape)] 99 | (keyword (ns-prefix service) shape-key))) 100 | 101 | (defn response-spec-key 102 | "Returns the key to look up in the spec registry for the spec for 103 | the response body of op." 104 | [service op] 105 | (load-specs service) 106 | (when-let [shape-key (some->> service :operations op :output :shape)] 107 | (keyword (ns-prefix service) shape-key))) 108 | -------------------------------------------------------------------------------- /test/.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((nil . ((cider-clojure-cli-aliases . ":clj:dev:test")))) 2 | -------------------------------------------------------------------------------- /test/resources/.aws/config: -------------------------------------------------------------------------------- 1 | [default] 2 | region = us-east-1 3 | 4 | [tardigrade] 5 | region = us-west-1 6 | 7 | [profile waterbear] 8 | region = eu-west-1 9 | credential_process=awsprocesscreds-saml -e 'https://url.com?with-query-string=true' -u some-user -a arn:aws:iam::1234:role/system/specialness 10 | 11 | [nested] 12 | s3 = 13 | max_concurrent_requests=10 14 | s3_key = s3_val 15 | region = eu-west-1 16 | s3 = 17 | max_queue_size=1000 18 | foo.bar = baz 19 | 20 | [temp-credentials] 21 | aws_session_token = FQoG/Ehj40mh/xf0TR+xLl+cp/xGWC+haIy+fJh6/fD+LFW= 22 | -------------------------------------------------------------------------------- /test/resources/.aws/credentials: -------------------------------------------------------------------------------- 1 | [default] 2 | aws_access_key_id = DEFAULT_AWS_ACCESS_KEY 3 | aws_secret_access_key = DEFAULT_AWS_SECRET_ACCESS_KEY 4 | 5 | [tardigrade] 6 | aws_access_key_id = TARDIGRADE_AWS_ACCESS_KEY 7 | aws_secret_access_key = TARDIGRADE_AWS_SECRET_ACCESS_KEY 8 | aws_session_token = TARDIGRADE_AWS_SESSION_TOKEN 9 | -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-key-duplicate/get-header-key-duplicate.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=c9d5ea9f3f72853aea855b47ea873832890dbdd183b4468f858259531a5138ea -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-key-duplicate/get-header-key-duplicate.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | my-header1:value2,value2,value1 6 | x-amz-date:20150830T123600Z 7 | 8 | host;my-header1;x-amz-date 9 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-key-duplicate/get-header-key-duplicate.req: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:value2 4 | My-Header1:value2 5 | My-Header1:value1 6 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-key-duplicate/get-header-key-duplicate.sreq: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:value2 4 | My-Header1:value2 5 | My-Header1:value1 6 | X-Amz-Date:20150830T123600Z 7 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=c9d5ea9f3f72853aea855b47ea873832890dbdd183b4468f858259531a5138ea -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-key-duplicate/get-header-key-duplicate.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | dc7f04a3abfde8d472b0ab1a418b741b7c67174dad1551b4117b15527fbe966c -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-multiline/get-header-value-multiline.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=ba17b383a53190154eb5fa66a1b836cc297cc0a3d70a5d00705980573d8ff790 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-multiline/get-header-value-multiline.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | my-header1:value1,value2,value3 6 | x-amz-date:20150830T123600Z 7 | 8 | host;my-header1;x-amz-date 9 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-multiline/get-header-value-multiline.req: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:value1 4 | value2 5 | value3 6 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-multiline/get-header-value-multiline.sreq: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:value1 4 | value2 5 | value3 6 | X-Amz-Date:20150830T123600Z 7 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=ba17b383a53190154eb5fa66a1b836cc297cc0a3d70a5d00705980573d8ff790 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-multiline/get-header-value-multiline.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | b7b6cbfd8a0430b78891e986784da2630c8a135a8595cec25b26ea94f926ee55 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-order/get-header-value-order.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=08c7e5a9acfcfeb3ab6b2185e75ce8b1deb5e634ec47601a50643f830c755c01 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-order/get-header-value-order.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | my-header1:value4,value1,value3,value2 6 | x-amz-date:20150830T123600Z 7 | 8 | host;my-header1;x-amz-date 9 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-order/get-header-value-order.req: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:value4 4 | My-Header1:value1 5 | My-Header1:value3 6 | My-Header1:value2 7 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-order/get-header-value-order.sreq: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:value4 4 | My-Header1:value1 5 | My-Header1:value3 6 | My-Header1:value2 7 | X-Amz-Date:20150830T123600Z 8 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=08c7e5a9acfcfeb3ab6b2185e75ce8b1deb5e634ec47601a50643f830c755c01 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-order/get-header-value-order.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 31ce73cd3f3d9f66977ad3dd957dc47af14df92fcd8509f59b349e9137c58b86 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-trim/get-header-value-trim.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;my-header2;x-amz-date, Signature=acc3ed3afb60bb290fc8d2dd0098b9911fcaa05412b367055dee359757a9c736 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-trim/get-header-value-trim.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | my-header1:value1 6 | my-header2:"a b c" 7 | x-amz-date:20150830T123600Z 8 | 9 | host;my-header1;my-header2;x-amz-date 10 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-trim/get-header-value-trim.req: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1: value1 4 | My-Header2: "a b c" 5 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-trim/get-header-value-trim.sreq: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1: value1 4 | My-Header2: "a b c" 5 | X-Amz-Date:20150830T123600Z 6 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;my-header2;x-amz-date, Signature=acc3ed3afb60bb290fc8d2dd0098b9911fcaa05412b367055dee359757a9c736 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-header-value-trim/get-header-value-trim.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | a726db9b0df21c14f559d0a978e563112acb1b9e05476f0a6a1c7d68f28605c7 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-unreserved/get-unreserved.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=07ef7494c76fa4850883e2b006601f940f8a34d404d0cfa977f52a65bbf5f24f -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-unreserved/get-unreserved.creq: -------------------------------------------------------------------------------- 1 | GET 2 | /-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-unreserved/get-unreserved.req: -------------------------------------------------------------------------------- 1 | GET /-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-unreserved/get-unreserved.sreq: -------------------------------------------------------------------------------- 1 | GET /-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=07ef7494c76fa4850883e2b006601f940f8a34d404d0cfa977f52a65bbf5f24f -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-unreserved/get-unreserved.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 6a968768eefaa713e2a6b16b589a8ea192661f098f37349f4e2c0082757446f9 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-utf8/get-utf8.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=8318018e0b0f223aa2bbf98705b62bb787dc9c0e678f255a891fd03141be5d85 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-utf8/get-utf8.creq: -------------------------------------------------------------------------------- 1 | GET 2 | /%E1%88%B4 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-utf8/get-utf8.req: -------------------------------------------------------------------------------- 1 | GET /ሴ HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-utf8/get-utf8.sreq: -------------------------------------------------------------------------------- 1 | GET /ሴ HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=8318018e0b0f223aa2bbf98705b62bb787dc9c0e678f255a891fd03141be5d85 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-utf8/get-utf8.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 2a0a97d02205e45ce2e994789806b19270cfbbb0921b278ccf58f5249ac42102 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-empty-query-key/get-vanilla-empty-query-key.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=a67d582fa61cc504c4bae71f336f98b97f1ea3c7a6bfe1b6e45aec72011b9aeb -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-empty-query-key/get-vanilla-empty-query-key.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | Param1=value1 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-empty-query-key/get-vanilla-empty-query-key.req: -------------------------------------------------------------------------------- 1 | GET /?Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-empty-query-key/get-vanilla-empty-query-key.sreq: -------------------------------------------------------------------------------- 1 | GET /?Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=a67d582fa61cc504c4bae71f336f98b97f1ea3c7a6bfe1b6e45aec72011b9aeb -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-empty-query-key/get-vanilla-empty-query-key.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 1e24db194ed7d0eec2de28d7369675a243488e08526e8c1c73571282f7c517ab -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key-case/get-vanilla-query-order-key-case.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=b97d918cfa904a5beff61c982a1b6f458b799221646efd99d3219ec94cdf2500 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key-case/get-vanilla-query-order-key-case.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | Param1=value1&Param2=value2 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key-case/get-vanilla-query-order-key-case.req: -------------------------------------------------------------------------------- 1 | GET /?Param2=value2&Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key-case/get-vanilla-query-order-key-case.sreq: -------------------------------------------------------------------------------- 1 | GET /?Param2=value2&Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=b97d918cfa904a5beff61c982a1b6f458b799221646efd99d3219ec94cdf2500 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key-case/get-vanilla-query-order-key-case.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 816cd5b414d056048ba4f7c5386d6e0533120fb1fcfa93762cf0fc39e2cf19e0 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key/get-vanilla-query-order-key.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=eedbc4e291e521cf13422ffca22be7d2eb8146eecf653089df300a15b2382bd1 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key/get-vanilla-query-order-key.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | Param1=Value1&Param1=value2 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key/get-vanilla-query-order-key.req: -------------------------------------------------------------------------------- 1 | GET /?Param1=value2&Param1=Value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key/get-vanilla-query-order-key.sreq: -------------------------------------------------------------------------------- 1 | GET /?Param1=value2&Param1=Value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=eedbc4e291e521cf13422ffca22be7d2eb8146eecf653089df300a15b2382bd1 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-key/get-vanilla-query-order-key.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 704b4cef673542d84cdff252633f065e8daeba5f168b77116f8b1bcaf3d38f89 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-value/get-vanilla-query-order-value.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5772eed61e12b33fae39ee5e7012498b51d56abc0abb7c60486157bd471c4694 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-value/get-vanilla-query-order-value.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | Param1=value1&Param1=value2 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-value/get-vanilla-query-order-value.req: -------------------------------------------------------------------------------- 1 | GET /?Param1=value2&Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-value/get-vanilla-query-order-value.sreq: -------------------------------------------------------------------------------- 1 | GET /?Param1=value2&Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5772eed61e12b33fae39ee5e7012498b51d56abc0abb7c60486157bd471c4694 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-order-value/get-vanilla-query-order-value.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | c968629d70850097a2d8781c9bf7edcb988b04cac14cca9be4acc3595f884606 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-unreserved/get-vanilla-query-unreserved.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=9c3e54bfcdf0b19771a7f523ee5669cdf59bc7cc0884027167c21bb143a40197 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-unreserved/get-vanilla-query-unreserved.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | -._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-unreserved/get-vanilla-query-unreserved.req: -------------------------------------------------------------------------------- 1 | GET /?-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-unreserved/get-vanilla-query-unreserved.sreq: -------------------------------------------------------------------------------- 1 | GET /?-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=-._~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=9c3e54bfcdf0b19771a7f523ee5669cdf59bc7cc0884027167c21bb143a40197 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query-unreserved/get-vanilla-query-unreserved.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | c30d4703d9f799439be92736156d47ccfb2d879ddf56f5befa6d1d6aab979177 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query/get-vanilla-query.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query/get-vanilla-query.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query/get-vanilla-query.req: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query/get-vanilla-query.sreq: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-query/get-vanilla-query.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | bb579772317eb040ac9ed261061d46c1f17a8133879d6129b6e1c25292927e63 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-utf8-query/get-vanilla-utf8-query.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=2cdec8eed098649ff3a119c94853b13c643bcf08f8b0a1d91e12c9027818dd04 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-utf8-query/get-vanilla-utf8-query.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | %E1%88%B4=bar 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-utf8-query/get-vanilla-utf8-query.req: -------------------------------------------------------------------------------- 1 | GET /?ሴ=bar HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-utf8-query/get-vanilla-utf8-query.sreq: -------------------------------------------------------------------------------- 1 | GET /?ሴ=bar HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=2cdec8eed098649ff3a119c94853b13c643bcf08f8b0a1d91e12c9027818dd04 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla-utf8-query/get-vanilla-utf8-query.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | eb30c5bed55734080471a834cc727ae56beb50e5f39d1bff6d0d38cb192a7073 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla/get-vanilla.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla/get-vanilla.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla/get-vanilla.req: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla/get-vanilla.sreq: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/get-vanilla/get-vanilla.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | bb579772317eb040ac9ed261061d46c1f17a8133879d6129b6e1c25292927e63 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative-relative/get-relative-relative.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative-relative/get-relative-relative.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative-relative/get-relative-relative.req: -------------------------------------------------------------------------------- 1 | GET /example1/example2/../.. HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative-relative/get-relative-relative.sreq: -------------------------------------------------------------------------------- 1 | GET /example1/example2/../.. HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative-relative/get-relative-relative.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | bb579772317eb040ac9ed261061d46c1f17a8133879d6129b6e1c25292927e63 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative/get-relative.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative/get-relative.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative/get-relative.req: -------------------------------------------------------------------------------- 1 | GET /example/.. HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative/get-relative.sreq: -------------------------------------------------------------------------------- 1 | GET /example/.. HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-relative/get-relative.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | bb579772317eb040ac9ed261061d46c1f17a8133879d6129b6e1c25292927e63 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-dot-slash/get-slash-dot-slash.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-dot-slash/get-slash-dot-slash.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-dot-slash/get-slash-dot-slash.req: -------------------------------------------------------------------------------- 1 | GET /./ HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-dot-slash/get-slash-dot-slash.sreq: -------------------------------------------------------------------------------- 1 | GET /./ HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-dot-slash/get-slash-dot-slash.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | bb579772317eb040ac9ed261061d46c1f17a8133879d6129b6e1c25292927e63 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-pointless-dot/get-slash-pointless-dot.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=ef75d96142cf21edca26f06005da7988e4f8dc83a165a80865db7089db637ec5 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-pointless-dot/get-slash-pointless-dot.creq: -------------------------------------------------------------------------------- 1 | GET 2 | /example 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-pointless-dot/get-slash-pointless-dot.req: -------------------------------------------------------------------------------- 1 | GET /./example HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-pointless-dot/get-slash-pointless-dot.sreq: -------------------------------------------------------------------------------- 1 | GET /./example HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=ef75d96142cf21edca26f06005da7988e4f8dc83a165a80865db7089db637ec5 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash-pointless-dot/get-slash-pointless-dot.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 214d50c111a8edc4819da6a636336472c916b5240f51e9a51b5c3305180cf702 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash/get-slash.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash/get-slash.creq: -------------------------------------------------------------------------------- 1 | GET 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash/get-slash.req: -------------------------------------------------------------------------------- 1 | GET // HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash/get-slash.sreq: -------------------------------------------------------------------------------- 1 | GET // HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5fa00fa31553b73ebf1942676e86291e8372ff2a2260956d9b8aae1d763fbf31 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slash/get-slash.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | bb579772317eb040ac9ed261061d46c1f17a8133879d6129b6e1c25292927e63 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slashes/get-slashes.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=9a624bd73a37c9a373b5312afbebe7a714a789de108f0bdfe846570885f57e84 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slashes/get-slashes.creq: -------------------------------------------------------------------------------- 1 | GET 2 | /example/ 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slashes/get-slashes.req: -------------------------------------------------------------------------------- 1 | GET //example// HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slashes/get-slashes.sreq: -------------------------------------------------------------------------------- 1 | GET //example// HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=9a624bd73a37c9a373b5312afbebe7a714a789de108f0bdfe846570885f57e84 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-slashes/get-slashes.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | cb96b4ac96d501f7c5c15bc6d67b3035061cfced4af6585ad927f7e6c985c015 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-space/get-space.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=652487583200325589f1fba4c7e578f72c47cb61beeca81406b39ddec1366741 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-space/get-space.creq: -------------------------------------------------------------------------------- 1 | GET 2 | /example%20space/ 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-space/get-space.req: -------------------------------------------------------------------------------- 1 | GET /example space/ HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-space/get-space.sreq: -------------------------------------------------------------------------------- 1 | GET /example space/ HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=652487583200325589f1fba4c7e578f72c47cb61beeca81406b39ddec1366741 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/get-space/get-space.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 63ee75631ed7234ae61b5f736dfc7754cdccfedbff4b5128a915706ee9390d86 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/normalize-path/normalize-path.txt: -------------------------------------------------------------------------------- 1 | A note about signing requests to Amazon S3: 2 | 3 | In exception to this, you do not normalize URI paths for requests to Amazon S3. For example, if you have a bucket with an object named my-object//example//photo.user, use that path. Normalizing the path to my-object/example/photo.user will cause the request to fail. For more information, see Task 1: Create a Canonical Request in the Amazon Simple Storage Service API Reference: http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html#canonical-request -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-case/post-header-key-case.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-case/post-header-key-case.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-case/post-header-key-case.req: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-case/post-header-key-case.sreq: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-case/post-header-key-case.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 553f88c9e4d10fc9e109e2aeb65f030801b70c2f6468faca261d401ae622fc87 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-sort/post-header-key-sort.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=c5410059b04c1ee005303aed430f6e6645f61f4dc9e1461ec8f8916fdf18852c -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-sort/post-header-key-sort.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | 4 | host:example.amazonaws.com 5 | my-header1:value1 6 | x-amz-date:20150830T123600Z 7 | 8 | host;my-header1;x-amz-date 9 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-sort/post-header-key-sort.req: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:value1 4 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-sort/post-header-key-sort.sreq: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:value1 4 | X-Amz-Date:20150830T123600Z 5 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=c5410059b04c1ee005303aed430f6e6645f61f4dc9e1461ec8f8916fdf18852c -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-key-sort/post-header-key-sort.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 9368318c2967cf6de74404b30c65a91e8f6253e0a8659d6d5319f1a812f87d65 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-value-case/post-header-value-case.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=cdbc9802e29d2942e5e10b5bccfdd67c5f22c7c4e8ae67b53629efa58b974b7d -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-value-case/post-header-value-case.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | 4 | host:example.amazonaws.com 5 | my-header1:VALUE1 6 | x-amz-date:20150830T123600Z 7 | 8 | host;my-header1;x-amz-date 9 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-value-case/post-header-value-case.req: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:VALUE1 4 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-value-case/post-header-value-case.sreq: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | My-Header1:VALUE1 4 | X-Amz-Date:20150830T123600Z 5 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;my-header1;x-amz-date, Signature=cdbc9802e29d2942e5e10b5bccfdd67c5f22c7c4e8ae67b53629efa58b974b7d -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-header-value-case/post-header-value-case.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | d51ced243e649e3de6ef63afbbdcbca03131a21a7103a1583706a64618606a93 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-after/post-sts-header-after.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-after/post-sts-header-after.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-after/post-sts-header-after.req: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-after/post-sts-header-after.sreq: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | X-Amz-Security-Token:AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA== 5 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-after/post-sts-header-after.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 553f88c9e4d10fc9e109e2aeb65f030801b70c2f6468faca261d401ae622fc87 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-before/post-sts-header-before.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=85d96828115b5dc0cfc3bd16ad9e210dd772bbebba041836c64533a82be05ead -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-before/post-sts-header-before.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | x-amz-security-token:AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA== 7 | 8 | host;x-amz-date;x-amz-security-token 9 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-before/post-sts-header-before.req: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | X-Amz-Security-Token:AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA== -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-before/post-sts-header-before.sreq: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | X-Amz-Security-Token:AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA== 5 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date;x-amz-security-token, Signature=85d96828115b5dc0cfc3bd16ad9e210dd772bbebba041836c64533a82be05ead -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/post-sts-header-before/post-sts-header-before.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | c237e1b440d4c63c32ca95b5b99481081cb7b13c7e40434868e71567c1a882f6 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-sts-token/readme.txt: -------------------------------------------------------------------------------- 1 | A note about using temporary security credentials: 2 | 3 | You can use temporary security credentials provided by the AWS Security Token Service (AWS STS) to sign a request. The process is the same as using long-term credentials but requires an additional HTTP header or query string parameter for the security token. The name of the header or query string parameter is X-Amz-Security-Token, and the value is the session token (the string that you received from AWS STS when you obtained temporary security credentials). 4 | 5 | When you add X-Amz-Security-Token, some services require that you include this parameter in the canonical (signed) request. For other services, you add this parameter at the end, after you calculate the signature. For details see the API reference documentation for that service. 6 | 7 | The test suite has 2 examples: 8 | 9 | post-sts-header-before - The X-Amz-Security-Token header is part of the canonical request. 10 | 11 | post-sts-header-after - The X-Amz-Security-Token header is added to the request after you calculate the signature. 12 | 13 | The test suite uses this example value for X-Amz-Security-Token: 14 | 15 | AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA== -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-empty-query-value/post-vanilla-empty-query-value.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=28038455d6de14eafc1f9222cf5aa6f1a96197d7deb8263271d420d138af7f11 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-empty-query-value/post-vanilla-empty-query-value.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | Param1=value1 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-empty-query-value/post-vanilla-empty-query-value.req: -------------------------------------------------------------------------------- 1 | POST /?Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-empty-query-value/post-vanilla-empty-query-value.sreq: -------------------------------------------------------------------------------- 1 | POST /?Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=28038455d6de14eafc1f9222cf5aa6f1a96197d7deb8263271d420d138af7f11 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-empty-query-value/post-vanilla-empty-query-value.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 9d659678c1756bb3113e2ce898845a0a79dbbc57b740555917687f1b3340fbbd -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-query/post-vanilla-query.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=28038455d6de14eafc1f9222cf5aa6f1a96197d7deb8263271d420d138af7f11 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-query/post-vanilla-query.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | Param1=value1 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-query/post-vanilla-query.req: -------------------------------------------------------------------------------- 1 | POST /?Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-query/post-vanilla-query.sreq: -------------------------------------------------------------------------------- 1 | POST /?Param1=value1 HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=28038455d6de14eafc1f9222cf5aa6f1a96197d7deb8263271d420d138af7f11 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla-query/post-vanilla-query.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 9d659678c1756bb3113e2ce898845a0a79dbbc57b740555917687f1b3340fbbd -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla/post-vanilla.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla/post-vanilla.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | 4 | host:example.amazonaws.com 5 | x-amz-date:20150830T123600Z 6 | 7 | host;x-amz-date 8 | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla/post-vanilla.req: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla/post-vanilla.sreq: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host:example.amazonaws.com 3 | X-Amz-Date:20150830T123600Z 4 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=host;x-amz-date, Signature=5da7c1a2acd57cee7505fc6676e4e544621c30862966e37dddb68e92efbe5d6b -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-vanilla/post-vanilla.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 553f88c9e4d10fc9e109e2aeb65f030801b70c2f6468faca261d401ae622fc87 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded-parameters/post-x-www-form-urlencoded-parameters.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=1a72ec8f64bd914b0e42e42607c7fbce7fb2c7465f63e3092b3b0d39fa77a6fe -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded-parameters/post-x-www-form-urlencoded-parameters.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | 4 | content-type:application/x-www-form-urlencoded; charset=utf8 5 | host:example.amazonaws.com 6 | x-amz-date:20150830T123600Z 7 | 8 | content-type;host;x-amz-date 9 | 9095672bbd1f56dfc5b65f3e153adc8731a4a654192329106275f4c7b24d0b6e -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded-parameters/post-x-www-form-urlencoded-parameters.req: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Content-Type:application/x-www-form-urlencoded; charset=utf8 3 | Host:example.amazonaws.com 4 | X-Amz-Date:20150830T123600Z 5 | 6 | Param1=value1 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded-parameters/post-x-www-form-urlencoded-parameters.sreq: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Content-Type:application/x-www-form-urlencoded; charset=utf8 3 | Host:example.amazonaws.com 4 | X-Amz-Date:20150830T123600Z 5 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=1a72ec8f64bd914b0e42e42607c7fbce7fb2c7465f63e3092b3b0d39fa77a6fe 6 | 7 | Param1=value1 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded-parameters/post-x-www-form-urlencoded-parameters.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 2e1cf7ed91881a30569e46552437e4156c823447bf1781b921b5d486c568dd1c -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded/post-x-www-form-urlencoded.authz: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=ff11897932ad3f4e8b18135d722051e5ac45fc38421b1da7b9d196a0fe09473a -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded/post-x-www-form-urlencoded.creq: -------------------------------------------------------------------------------- 1 | POST 2 | / 3 | 4 | content-type:application/x-www-form-urlencoded 5 | host:example.amazonaws.com 6 | x-amz-date:20150830T123600Z 7 | 8 | content-type;host;x-amz-date 9 | 9095672bbd1f56dfc5b65f3e153adc8731a4a654192329106275f4c7b24d0b6e -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded/post-x-www-form-urlencoded.req: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Content-Type:application/x-www-form-urlencoded 3 | Host:example.amazonaws.com 4 | X-Amz-Date:20150830T123600Z 5 | 6 | Param1=value1 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded/post-x-www-form-urlencoded.sreq: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Content-Type:application/x-www-form-urlencoded 3 | Host:example.amazonaws.com 4 | X-Amz-Date:20150830T123600Z 5 | Authorization: AWS4-HMAC-SHA256 Credential=AKIDEXAMPLE/20150830/us-east-1/service/aws4_request, SignedHeaders=content-type;host;x-amz-date, Signature=ff11897932ad3f4e8b18135d722051e5ac45fc38421b1da7b9d196a0fe09473a 6 | 7 | Param1=value1 -------------------------------------------------------------------------------- /test/resources/aws-sig-v4-test-suite/post-x-www-form-urlencoded/post-x-www-form-urlencoded.sts: -------------------------------------------------------------------------------- 1 | AWS4-HMAC-SHA256 2 | 20150830T123600Z 3 | 20150830/us-east-1/service/aws4_request 4 | 42a5e5bb34198acb3e84da4f085bb7927f2bc277ca766e6d19c73c2154021281 -------------------------------------------------------------------------------- /test/resources/botocore/protocols/output/event-stream.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "REST XML Event Stream", 4 | "metadata": { 5 | "protocol": "rest-xml" 6 | }, 7 | "shapes": { 8 | "OutputShape": { 9 | "type": "structure", 10 | "members": { 11 | "Payload": {"shape": "EventStream"} 12 | }, 13 | "payload": "Payload" 14 | }, 15 | "EventStream": { 16 | "type": "structure", 17 | "eventstream": true, 18 | "members": { 19 | "TypeA": {"shape": "TypeAEvent"}, 20 | "TypeB": {"shape": "TypeBEvent"}, 21 | "TypeC": {"shape": "TypeCEvent"} 22 | } 23 | }, 24 | "TypeAEvent": { 25 | "type": "structure", 26 | "event": true, 27 | "members": { 28 | "Payload": { 29 | "shape": "BlobType", 30 | "eventpayload": true 31 | } 32 | } 33 | }, 34 | "TypeBEvent": { 35 | "type": "structure", 36 | "event": true, 37 | "members": { 38 | "Details": { 39 | "shape": "Details", 40 | "eventpayload": true 41 | } 42 | } 43 | }, 44 | "TypeCEvent": { 45 | "type": "structure", 46 | "event": true, 47 | "members": { 48 | "Details": { 49 | "shape": "Details", 50 | "eventpayload": true 51 | }, 52 | "Boolean": { 53 | "shape": "BooleanType", 54 | "eventheader": true 55 | }, 56 | "Integer": { 57 | "shape": "IntegerType", 58 | "eventheader": true 59 | }, 60 | "Blob": { 61 | "shape": "BlobType", 62 | "eventheader": true 63 | }, 64 | "String": { 65 | "shape": "StringType", 66 | "eventheader": true 67 | }, 68 | "Timestamp": { 69 | "shape": "TimestampType", 70 | "eventheader": true 71 | } 72 | } 73 | }, 74 | "Details": { 75 | "type": "structure", 76 | "members": { 77 | "StringField": {"shape": "StringType"}, 78 | "IntegerField": {"shape": "IntegerType"} 79 | } 80 | }, 81 | "StringType": { 82 | "type": "string" 83 | }, 84 | "IntegerType": { 85 | "type": "integer" 86 | }, 87 | "BooleanType": { 88 | "type": "boolean" 89 | }, 90 | "TimestampType": { 91 | "type": "timestamp" 92 | }, 93 | "BlobType": { 94 | "type": "blob" 95 | } 96 | }, 97 | "cases": [ 98 | { 99 | "given": { 100 | "output": { 101 | "shape": "OutputShape" 102 | }, 103 | "name": "OperationName" 104 | }, 105 | "result": { 106 | "Payload": [ 107 | { 108 | "TypeA": {"Payload": "somebytes"} 109 | }, 110 | { 111 | "TypeB": { 112 | "Details": { 113 | "StringField": "somestring", 114 | "IntegerField": 123 115 | } 116 | } 117 | } 118 | ] 119 | }, 120 | "response": { 121 | "status_code": 200, 122 | "headers": {}, 123 | "body": "AAAAbAAAAFPLgkVrDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcABVR5cGVBDTpjb250ZW50LXR5cGUHABhhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW1zb21lYnl0ZXMesj2HAAAAsAAAAEOaMMdXDTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcABVR5cGVCDTpjb250ZW50LXR5cGUHAAh0ZXh0L3htbDxUeXBlQiB4bWxucz0iIj48U3RyaW5nRmllbGQ+c29tZXN0cmluZzwvU3RyaW5nRmllbGQ+PEludGVnZXJGaWVsZD4xMjM8L0ludGVnZXJGaWVsZD48L1R5cGVCPiwthPo=" 124 | } 125 | }, 126 | { 127 | "given": { 128 | "output": { 129 | "shape": "OutputShape" 130 | }, 131 | "name": "OperationName" 132 | }, 133 | "result": { 134 | "Payload": [ 135 | { 136 | "TypeC": { 137 | "Boolean": true, 138 | "Integer": 123, 139 | "Blob": "someblob", 140 | "String": "somestring", 141 | "Timestamp": 1422172800, 142 | "Details": { 143 | "StringField": "somestring", 144 | "IntegerField": 123 145 | } 146 | } 147 | } 148 | ] 149 | }, 150 | "response": { 151 | "status_code": 200, 152 | "headers": {}, 153 | "body": "AAABAQAAAJBjEbY4DTptZXNzYWdlLXR5cGUHAAVldmVudAs6ZXZlbnQtdHlwZQcABVR5cGVDDTpjb250ZW50LXR5cGUHAAh0ZXh0L3htbAdCb29sZWFuAAdJbnRlZ2VyBAAAAHsEQmxvYgYACHNvbWVibG9iBlN0cmluZwcACnNvbWVzdHJpbmcJVGltZXN0YW1wCAAAAUsgGsQAPERldGFpbHMgeG1sbnM9IiI+PFN0cmluZ0ZpZWxkPnNvbWVzdHJpbmc8L1N0cmluZ0ZpZWxkPjxJbnRlZ2VyRmllbGQ+MTIzPC9JbnRlZ2VyRmllbGQ+PC9EZXRhaWxzPhGUvKo=" 154 | } 155 | }, 156 | { 157 | "given": { 158 | "output": { 159 | "shape": "OutputShape" 160 | }, 161 | "name": "OperationName" 162 | }, 163 | "result": { 164 | "Payload": [] 165 | }, 166 | "response": { 167 | "status_code": 200, 168 | "headers": {}, 169 | "body": "" 170 | } 171 | } 172 | ] 173 | } 174 | ] 175 | -------------------------------------------------------------------------------- /test/resources/cognitect/protocols/input/query.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "URL Encoded values in body", 4 | "metadata": { 5 | "protocol": "query", 6 | "apiVersion": "2014-01-01" 7 | }, 8 | "shapes": { 9 | "InputShape": { 10 | "type": "structure", 11 | "members": { 12 | "Integer": { 13 | "shape": "IntegerType" 14 | }, 15 | "Long": { 16 | "shape": "LongType" 17 | }, 18 | "Double": { 19 | "shape" : "DoubleType" 20 | }, 21 | "Float": { 22 | "shape" : "FloatType" 23 | }, 24 | "String": { 25 | "shape": "StringType" 26 | }, 27 | "Boolean": { 28 | "shape": "BooleanType" 29 | }, 30 | "Timestamp": { 31 | "shape" : "TimestampType" 32 | }, 33 | "Blob": { 34 | "shape": "BlobType" 35 | } 36 | } 37 | }, 38 | "IntegerType": { 39 | "type": "integer" 40 | }, 41 | "LongType": { 42 | "type": "long" 43 | }, 44 | "DoubleType": { 45 | "type": "double" 46 | }, 47 | "FloatType": { 48 | "type": "float" 49 | }, 50 | "StringType": { 51 | "type": "string" 52 | }, 53 | "BooleanType": { 54 | "type": "boolean" 55 | }, 56 | "TimestampType": { 57 | "type": "timestamp" 58 | }, 59 | "BlobType": { 60 | "type": "blob" 61 | } 62 | }, 63 | "cases": [ 64 | { 65 | "given": { 66 | "input": { 67 | "shape": "InputShape" 68 | }, 69 | "name": "OperationName" 70 | }, 71 | "params": { 72 | "Integer": 22, 73 | "Long": 37, 74 | "Double": 1.2, 75 | "Float": 3.4, 76 | "String": "a+b", 77 | "Boolean": true, 78 | "Timestamp": 1422172800, 79 | "Blob": "foo" 80 | }, 81 | "serialized": { 82 | "uri": "/", 83 | "headers": { 84 | "Content-Type": "application/x-www-form-urlencoded; charset=utf-8" 85 | }, 86 | "body": "Action=OperationName&Version=2014-01-01&Integer=22&Long=37&Double=1.2&Float=3.4&String=a%2Bb&Boolean=true&Timestamp=2015-01-25T08%3A00%3A00Z&Blob=Zm9v" 87 | } 88 | } 89 | ] 90 | } 91 | ] 92 | -------------------------------------------------------------------------------- /test/resources/cognitect/protocols/input/rest-xml.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Lists in headers", 4 | "metadata": { 5 | "protocol": "rest-xml", 6 | "apiVersion": "2014-01-01" 7 | }, 8 | "shapes": { 9 | "ObjectAttributes": { 10 | "type": "string", 11 | "enum": [ 12 | "ETag", 13 | "Checksum", 14 | "ObjectParts", 15 | "StorageClass", 16 | "ObjectSize" 17 | ] 18 | }, 19 | "ObjectAttributesList": { 20 | "type": "list", 21 | "member": { 22 | "shape": "ObjectAttributes" 23 | } 24 | }, 25 | "GetObjectAttributesRequest": { 26 | "type": "structure", 27 | "members": { 28 | "ObjectAttributes": { 29 | "shape": "ObjectAttributesList", 30 | "location": "header", 31 | "locationName": "x-amz-object-attributes" 32 | } 33 | } 34 | } 35 | }, 36 | "cases": [ 37 | { 38 | "given": { 39 | "http": { 40 | "method": "GET", 41 | "requestUri": "/2014-01-01/hostedzone" 42 | }, 43 | "input": { 44 | "shape": "GetObjectAttributesRequest" 45 | }, 46 | "name": "GetObjectAttributes" 47 | }, 48 | "params": { 49 | "ObjectAttributes": [ 50 | "Checksum", 51 | "ObjectParts" 52 | ] 53 | }, 54 | "serialized": { 55 | "method": "GET", 56 | "uri": "/2014-01-01/hostedzone", 57 | "headers": {"x-amz-object-attributes": "Checksum,ObjectParts"} 58 | } 59 | } 60 | ] 61 | } 62 | ] 63 | -------------------------------------------------------------------------------- /test/resources/cognitect/protocols/output/json.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "No output", 4 | "metadata": { 5 | "metadata": { 6 | "protocol": "json" 7 | }, 8 | "shapes": { 9 | "OutputShape": { 10 | "type": "structure", 11 | "members": { 12 | "StrType": { 13 | "shape": "StrType" 14 | } 15 | } 16 | }, 17 | "StrType": { 18 | "type": "string" 19 | } 20 | }, 21 | "cases": [ 22 | { 23 | "given": { 24 | "name": "OperationName" 25 | }, 26 | "result": {}, 27 | "response": { 28 | "status_code": 200, 29 | "headers": {} 30 | } 31 | } 32 | ] 33 | } 34 | }, 35 | { 36 | "description": "Timestamp members with doubles", 37 | "metadata": { 38 | "protocol": "json" 39 | }, 40 | "shapes": { 41 | "OutputShape": { 42 | "type": "structure", 43 | "members": { 44 | "TimeArg": { 45 | "shape": "TimestampType" 46 | } 47 | } 48 | }, 49 | "TimestampType": { 50 | "type": "timestamp" 51 | } 52 | }, 53 | "cases": [ 54 | { 55 | "given": { 56 | "output": { 57 | "shape": "OutputShape" 58 | }, 59 | "name": "OperationName" 60 | }, 61 | "result": { 62 | "TimeArg": 1398796238 63 | }, 64 | "response": { 65 | "status_code": 200, 66 | "headers": {}, 67 | "body": "{\"TimeArg\": 1.398796238E9}" 68 | } 69 | } 70 | ] 71 | } 72 | ] 73 | -------------------------------------------------------------------------------- /test/resources/cognitect/protocols/output/rest-json.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "No output", 4 | "metadata": { 5 | "metadata": { 6 | "protocol": "rest-json" 7 | }, 8 | "shapes": { 9 | "OutputShape": { 10 | "type": "structure", 11 | "members": { 12 | "StrType": { 13 | "shape": "StrType" 14 | } 15 | } 16 | }, 17 | "StrType": { 18 | "type": "string" 19 | } 20 | }, 21 | "cases": [ 22 | { 23 | "given": { 24 | "name": "OperationName" 25 | }, 26 | "result": {}, 27 | "response": { 28 | "status_code": 200, 29 | "headers": {} 30 | } 31 | } 32 | ] 33 | } 34 | }, 35 | { 36 | "description": "Named locations in JSON body", 37 | "metadata": { 38 | "protocol": "rest-json" 39 | }, 40 | "shapes": { 41 | "OutputShape": { 42 | "type": "structure", 43 | "payload": "Data", 44 | "members": { 45 | "Data": { 46 | "shape": "BodyStructure" 47 | } 48 | } 49 | }, 50 | "BodyStructure": { 51 | "type": "structure", 52 | "members": { 53 | "Foo": { 54 | "shape": "StringType", 55 | "locationName": "FOO" 56 | } 57 | } 58 | }, 59 | "StringType": { 60 | "type": "string" 61 | } 62 | }, 63 | "cases": [ 64 | { 65 | "given": { 66 | "output": { 67 | "shape": "OutputShape" 68 | }, 69 | "name": "OperationName" 70 | }, 71 | "result": { 72 | "Data": { 73 | "Foo": "abc" 74 | } 75 | }, 76 | "response": { 77 | "status_code": 200, 78 | "headers": {}, 79 | "body": "{\"FOO\": \"abc\"}" 80 | } 81 | } 82 | ] 83 | } 84 | ] 85 | -------------------------------------------------------------------------------- /test/resources/cognitect/protocols/output/rest-xml.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Single text node", 4 | "metadata": {"protocol": "rest-xml"}, 5 | "shapes": { 6 | "OutputShape": { 7 | "type": "structure", 8 | "members": { 9 | "LocationConstraint": { 10 | "shape": "StringType" 11 | } 12 | } 13 | }, 14 | "StringType": { 15 | "type": "string" 16 | } 17 | }, 18 | "cases": [ 19 | { 20 | "given": { 21 | "output": { 22 | "shape": "OutputShape" 23 | }, 24 | "name": "OperationName" 25 | }, 26 | "result": { 27 | "LocationConstraint": "us-west-1" 28 | }, 29 | "response": { 30 | "status_code": 200, 31 | "body": "us-west-1" 32 | } 33 | } 34 | ] 35 | }, 36 | { 37 | "description": "Single string in payload", 38 | "metadata": {"protocol": "rest-xml"}, 39 | "shapes": { 40 | "OutputShape": { 41 | "type": "structure", 42 | "members": { 43 | "Data": { 44 | "shape": "DataShape" 45 | } 46 | }, 47 | "payload" : "Data" 48 | }, 49 | "DataShape": { 50 | "type": "string" 51 | } 52 | }, 53 | "cases": [ 54 | { 55 | "given": { 56 | "output": { 57 | "shape": "OutputShape" 58 | }, 59 | "name": "OperationName" 60 | }, 61 | "result": { 62 | "Data": "{\"a\":1}" 63 | }, 64 | "response": { 65 | "status_code": 200, 66 | "body": "{\"a\":1}" 67 | } 68 | } 69 | ] 70 | } 71 | ] 72 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/client/api/localstack_elf.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.client.api.localstack-elf 2 | (:require 3 | [com.grzm.awyeah.credentials :as credentials])) 4 | 5 | (def client-default-opts 6 | {:credentials-provider (credentials/basic-credentials-provider 7 | {:access-key-id "ABC" 8 | :secret-access-key "XYZ"}) 9 | :endpoint-override {:protocol :http 10 | :hostname "localhost" 11 | :port 4566}}) 12 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/client/api/localstack_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.client.api.localstack-test 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.pprint :as pprint] 5 | [clojure.test :as test :refer [deftest is testing]] 6 | [cognitect.anomalies :as-alias anom] 7 | [com.grzm.awyeah.client.api :as aws] 8 | [com.grzm.awyeah.client.api.localstack-elf :as localstack-elf] 9 | [com.grzm.awyeah.http.awyeah :as http.awyeah])) 10 | 11 | (defn anomaly? [x] 12 | (when (and (map? x) (:cognitect.anomalies/category x)) 13 | x)) 14 | 15 | (defn client [opts] 16 | (aws/client (merge localstack-elf/client-default-opts opts))) 17 | 18 | (defn test-with-http-client 19 | [http-client label] 20 | 21 | (testing "ec2 (ec2 protocol)" 22 | (let [ec2 (client (cond-> {:api :ec2} 23 | http-client (assoc :http-client http-client))) 24 | keypair-name label] 25 | (let [res (aws/invoke ec2 {:op :DescribeKeyPairs})] 26 | (is (nil? (anomaly? res))) 27 | (is (nil? (->> (:KeyPairs res) 28 | (some #(= (:KeyName %) keypair-name)))))) 29 | (let [res (aws/invoke ec2 {:op :CreateKeyPair, :request {:KeyName keypair-name}})] 30 | (is (= keypair-name (:KeyName res)))) 31 | (let [res (aws/invoke ec2 {:op :DescribeKeyPairs})] 32 | (is (nil? (anomaly? res))) 33 | (is (true? (->> (:KeyPairs res) 34 | (some #(= (:KeyName %) keypair-name)))))))) 35 | 36 | (testing "lambda (rest-json protocol)" 37 | (let [lambda (client (cond-> {:api :lambda} 38 | http-client (assoc :http-client http-client))) 39 | res (aws/invoke lambda {:op :ListFunctions})] 40 | (is (nil? (anomaly? res))))) 41 | 42 | (testing "sts (query protocol)" 43 | (let [sts (client (cond-> {:api :sts} 44 | http-client (assoc :http-client http-client)))] 45 | (is (= #{:UserId :Account :Arn} (set (keys (aws/invoke sts {:op :GetCallerIdentity}))))))) 46 | 47 | (testing "ssm (json protocol)" 48 | (let [ssm (client (cond-> {:api :ssm} 49 | http-client (assoc :http-client http-client))) 50 | root (str "/" label) 51 | foo-param-name (str root "/" "foo") 52 | foo-param-value "yada-foo"] 53 | (let [{:keys [Parameters]} (aws/invoke ssm {:op :GetParametersByPath 54 | :request {:Path root 55 | :Recursive true}})] 56 | (is (= [] Parameters))) 57 | (is (= 1 (:Version (aws/invoke ssm {:op :PutParameter 58 | :request {:Name foo-param-name 59 | :Type "String" 60 | :Value foo-param-value}})))) 61 | (is (= [{:Type "String" 62 | :Name foo-param-name 63 | :Value foo-param-value}] 64 | (->> (aws/invoke ssm {:op :GetParametersByPath 65 | :request {:Path root 66 | :Recursive true}}) 67 | :Parameters 68 | (mapv #(select-keys % [:Type :Name :Value]))))))) 69 | 70 | (testing "s3 (rest-xml protocol)" 71 | (let [s3 (client (cond-> {:api :s3} 72 | http-client (assoc :http-client http-client))) 73 | bucket-name (str "some-bucket-" label) 74 | greeting-key "greeting" 75 | greeting-body "hai" 76 | farewell-key "farewell" 77 | farewell-body "kthxbye"] 78 | (let [res (aws/invoke s3 {:op :CreateBucket, 79 | :request {:Bucket bucket-name}})] 80 | (is (= (str "/" bucket-name) (:Location res)))) 81 | (testing "bytes body" 82 | (is (= :ETag (->> (aws/invoke s3 {:op :PutObject, 83 | :request {:Bucket bucket-name, 84 | :Key greeting-key 85 | :Body (.getBytes greeting-body)}}) 86 | keys 87 | (some #{:ETag})))) 88 | (let [{:keys [Body]} (aws/invoke s3 {:op :GetObject 89 | :request {:Bucket bucket-name 90 | :Key greeting-key}})] 91 | (is (= greeting-body (slurp Body))))) 92 | (testing "input-stream body" 93 | (is (= :ETag (->> (aws/invoke s3 {:op :PutObject, 94 | :request {:Bucket bucket-name 95 | :Key farewell-key 96 | :Body (io/input-stream (.getBytes farewell-body))}}) 97 | keys 98 | (some #{:ETag})))) 99 | (let [{:keys [Body]} (aws/invoke s3 {:op :GetObject 100 | :request {:Bucket bucket-name 101 | :Key farewell-key}})] 102 | (is (= farewell-body (slurp Body))))) 103 | (testing "HeadObject on non-existant object" 104 | (is (= ::anom/not-found (::anom/category 105 | (aws/invoke s3 {:op :HeadObject, 106 | :request {:Bucket bucket-name, 107 | :Key "no-such-object"}})))))))) 108 | 109 | (defn gen-label 110 | ([] 111 | (gen-label (java.time.Instant/now))) 112 | ([as-of] 113 | (java.lang.Long/toString (.getEpochSecond as-of) 26))) 114 | 115 | (deftest ^:integration awyeah-http-client-test 116 | (let [http-client (http.awyeah/create) 117 | label (str "java.net.http-" (gen-label))] 118 | (testing "java.net.http" 119 | (test-with-http-client http-client label)))) 120 | 121 | (deftest ^:integration default-client-test 122 | (let [label (str "default-http-client-" (gen-label))] 123 | (testing "default-http-client" 124 | (test-with-http-client nil label)))) 125 | 126 | (comment 127 | (test/run-tests) 128 | :end) 129 | 130 | (def this-ns *ns*) 131 | 132 | (defn run-tests 133 | ([] 134 | (run-tests {:test-ns this-ns})) 135 | ([{:keys [test-ns]}] 136 | (let [res (test/run-tests test-ns)] 137 | (pprint/pprint 138 | res) 139 | (when (->> ((juxt :fail :error) res) 140 | (some #(< 0 %))) 141 | (System/exit 1))))) 142 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/client/api_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.client.api-test 2 | (:require 3 | [clojure.datafy :as datafy] 4 | [clojure.test :refer [deftest is testing]] 5 | [com.grzm.awyeah.client.api :as aws] 6 | [com.grzm.awyeah.client.protocol :as client.protocol] 7 | [com.grzm.awyeah.client.shared :as shared] 8 | [com.grzm.awyeah.http :as http])) 9 | 10 | (deftest test-underlying-http-client 11 | (testing "defaults to shared client" 12 | (let [clients (repeatedly 5 #(aws/client {:api :s3 :region "us-east-1"}))] 13 | (is (= #{(shared/http-client)} 14 | (into #{(shared/http-client)} 15 | (->> clients (map (fn [c] (-> c client.protocol/-get-info :http-client)))))))))) 16 | 17 | (deftest test-datafy 18 | (let [client (aws/client {:api :s3}) 19 | data (datafy/datafy client)] 20 | (is (= "s3" (:api data))) 21 | (is (map? (:service data))) 22 | (is (map? (:metadata (:service data)))) 23 | (is (map? (:endpoint data))) 24 | (is (string? (:region data))) 25 | (is (map? (:ops data))) 26 | (is (map? (:endpoint data))))) 27 | 28 | (deftest test-stop 29 | (let [call-count (atom 0) 30 | default (aws/client {:api :s3 :region "us-east-1"}) 31 | supplied-shared (aws/client {:api :s3 :region "us-east-1" 32 | :http-client (shared/http-client)}) 33 | supplied-unshared (aws/client {:api :s3 :region "us-east-1" 34 | :http-client (http/resolve-http-client nil)})] 35 | (with-redefs [http/stop (fn [_] (swap! call-count inc))] 36 | (testing "has no effect when aws-client uses the default shared http-client" 37 | (aws/stop default) 38 | (is (zero? @call-count))) 39 | (testing "has no effect when user supplies the shared http-client" 40 | (aws/stop supplied-shared) 41 | (is (zero? @call-count))) 42 | (testing "forwards to http/stop when user supplies a different http-client" 43 | (aws/stop supplied-unshared) 44 | (is (= 1 @call-count)))) 45 | ;; now actually stop the aws-client with the non-shared http-client 46 | (aws/stop supplied-unshared))) 47 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/client/test_double_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.client.test-double-test 2 | "Tests for the test double client implementation." 3 | (:require 4 | [clojure.test :refer [deftest is testing]] 5 | [com.grzm.awyeah.client.api :as aws] 6 | [com.grzm.awyeah.client.impl-test :as client-test] 7 | [com.grzm.awyeah.client.test-double :as client.test])) 8 | 9 | (deftest keyword-access 10 | (let [c (client.test/client {:api :s3})] 11 | (is (= "s3" (:api c))) 12 | (is (map? (:service c))))) 13 | 14 | (deftest register-handlers-on-creation 15 | (testing "supports handler functions (sync and async)" 16 | (let [user-provided-response {:Location "abc"} 17 | test-s3 (client.test/client {:api :s3 :ops {:CreateBucket 18 | (constantly user-provided-response)}})] 19 | (doseq [invocation-response (client-test/invocations test-s3 20 | {:op :CreateBucket 21 | :request {:Bucket "bucket"}})] 22 | (is (= user-provided-response invocation-response)))))) 23 | 24 | (deftest register-handlers-with-instrument 25 | (let [user-provided-response {:Location "abc"} 26 | test-s3 (client.test/client {:api :s3})] 27 | (testing "register handler" 28 | (client.test/instrument test-s3 {:CreateBucket (constantly user-provided-response)}) 29 | (doseq [invocation-response (client-test/invocations test-s3 30 | {:op :CreateBucket 31 | :request {:Bucket "bucket"}})] 32 | (is (= user-provided-response invocation-response)))))) 33 | 34 | (deftest raw-response-values 35 | (let [test-s3 (client.test/client {:api :s3})] 36 | (let [response {:Buckets []}] 37 | (client.test/instrument test-s3 {:ListBuckets response}) 38 | (is (= response (aws/invoke test-s3 {:op :ListBuckets})))) 39 | (let [response {:Location "abc"}] 40 | (client.test/instrument test-s3 {:CreateBucket response}) 41 | (is (= response (aws/invoke test-s3 {:op :CreateBucket :request {:Bucket "bucket"}})))) 42 | (let [response {:cognitect.anomalies/category :cognitect.anomalies/busy}] 43 | (client.test/instrument test-s3 {:CreateBucket response}) 44 | (is (= response (aws/invoke test-s3 {:op :CreateBucket :request {:Bucket "bucket"}})))))) 45 | 46 | (deftest custom-testing-strategies 47 | (let [calls (atom []) 48 | test-s3 (client.test/client {:api :s3 49 | :ops {:CreateBucket 50 | (fn [op-map] 51 | (swap! calls conj op-map) 52 | {:Location "over the rainbow"})}})] 53 | (dotimes [n 5] 54 | (aws/invoke test-s3 {:op :CreateBucket 55 | :request {:Bucket (str "bucket-" n)}}) 56 | (is (= (take (inc n) [{:op :CreateBucket, :request {:Bucket "bucket-0"}} 57 | {:op :CreateBucket, :request {:Bucket "bucket-1"}} 58 | {:op :CreateBucket, :request {:Bucket "bucket-2"}} 59 | {:op :CreateBucket, :request {:Bucket "bucket-3"}} 60 | {:op :CreateBucket, :request {:Bucket "bucket-4"}}]) 61 | @calls))))) 62 | 63 | (deftest test-anomalies-and-exceptions 64 | (testing "returns an anomaly with spec report when invoked with invalid request" 65 | (let [res (aws/invoke 66 | (client.test/client {:api :s3}) 67 | {:op :CreateBucket 68 | :request {:BucketName "key should be :Bucket"}})] 69 | (is (= :cognitect.anomalies/incorrect (:cognitect.anomalies/category res))) 70 | (is (:clojure.spec.alpha/problems res)))) 71 | 72 | (testing "returns an anomaly when invoking op unsupported by service" 73 | (let [res (aws/invoke 74 | (client.test/client {:api :s3 :ops {}}) 75 | {:op :DoesNotExistInS3})] 76 | (is (= :cognitect.anomalies/unsupported (:cognitect.anomalies/category res))) 77 | (is (= "Operation not supported" (:cognitect.anomalies/message res))))) 78 | 79 | (testing "returns an anomaly when invoking undeclared operation" 80 | (let [res (aws/invoke 81 | (client.test/client {:api :s3 :ops {}}) 82 | {:op :CreateBucket 83 | :request {:Bucket "bucket"}})] 84 | (is (= :cognitect.anomalies/incorrect (:cognitect.anomalies/category res))) 85 | (is (= "No handler or response provided for op" (:cognitect.anomalies/message res))))) 86 | 87 | (testing "throws when instrumenting op unsupported by service" 88 | (is (thrown-with-msg? RuntimeException 89 | #"Operation not supported" 90 | (client.test/client {:api :s3 :ops {:DoesNotExistInS3 {}}}))) 91 | (let [client (client.test/client {:api :s3})] 92 | (is (thrown-with-msg? RuntimeException 93 | #"Operation not supported" 94 | (client.test/instrument client {:DoesNotExistInS3 {}})))))) 95 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/config_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.config-test 5 | (:require 6 | [clojure.test :refer [deftest is]] 7 | [clojure.java.io :as io] 8 | [com.grzm.awyeah.config :as config])) 9 | 10 | (deftest read-config 11 | (let [config (config/parse (io/resource ".aws/config"))] 12 | (is (= {"region" "us-west-1"} 13 | (get config "tardigrade"))) 14 | (is (= {"s3" {"max_concurrent_requests" "10" 15 | "max_queue_size" "1000" 16 | "s3_key" "s3_val"} 17 | "region" "eu-west-1" 18 | "foo.bar" "baz"} 19 | (get config "nested"))) 20 | (is (re-matches 21 | #"^awsprocesscreds.*specialness$" 22 | (get-in config ["waterbear" "credential_process"]))) 23 | (is (= "FQoG/Ehj40mh/xf0TR+xLl+cp/xGWC+haIy+fJh6/fD+LFW=" 24 | (get-in config ["temp-credentials" "aws_session_token"]))))) 25 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/ec2_metadata_utils_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.ec2-metadata-utils-test 5 | (:require 6 | [clojure.core.async :as a] 7 | [clojure.test :refer [deftest is testing use-fixtures]] 8 | [com.grzm.awyeah.client.shared :as shared] 9 | [com.grzm.awyeah.ec2-metadata-utils :as ec2-metadata-utils] 10 | [com.grzm.awyeah.http :as http] 11 | [com.grzm.awyeah.test.ec2-metadata-utils-server :as ec2-metadata-utils-server] 12 | [com.grzm.awyeah.util :as u]) 13 | (:import 14 | (java.net URI))) 15 | 16 | (def ^:dynamic *test-server-port*) 17 | (def ^:dynamic *http-client*) 18 | 19 | (defn test-server 20 | [f] 21 | ;; NOTE: starting w/ 0 generates a random port 22 | (let [server-stop-fn (ec2-metadata-utils-server/start 0) 23 | test-server-port (-> server-stop-fn meta :local-port)] 24 | (try 25 | (System/setProperty ec2-metadata-utils/ec2-metadata-service-override-system-property 26 | (str "http://localhost:" test-server-port)) 27 | (binding [*test-server-port* test-server-port 28 | *http-client* (shared/http-client)] 29 | (f)) 30 | (finally 31 | (server-stop-fn) 32 | (System/clearProperty ec2-metadata-utils/ec2-metadata-service-override-system-property))))) 33 | 34 | (use-fixtures :once test-server) 35 | 36 | (deftest returns-nil-after-retries 37 | (with-redefs [http/submit (constantly 38 | (doto (a/promise-chan) 39 | (a/>!! {:cognitect.anomalies/category :cognitect.anomalies/busy})))] 40 | (is (nil? (ec2-metadata-utils/get-ec2-instance-region *http-client*))))) 41 | 42 | (deftest request-map 43 | (testing "port" 44 | (is (= 443 (:server-port (#'ec2-metadata-utils/request-map (URI/create "https://169.254.169.254"))))) 45 | (is (= 80 (:server-port (#'ec2-metadata-utils/request-map (URI/create "http://169.254.169.254"))))) 46 | (is (= 8081 (:server-port (#'ec2-metadata-utils/request-map (URI/create "http://169.254.169.254:8081")))))) 47 | (testing "auth token" 48 | (is (nil? (get-in (#'ec2-metadata-utils/request-map (URI/create "http://localhost")) [:headers "Authorization"]))) 49 | (with-redefs [u/getenv {"AWS_CONTAINER_AUTHORIZATION_TOKEN" "this-is-the-token"}] 50 | (is (#{"this-is-the-token"} (get-in (#'ec2-metadata-utils/request-map (URI/create "http://localhost")) [:headers "Authorization"])))))) 51 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/endpoint_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.endpoint-test 5 | (:require 6 | [clojure.test :refer [deftest is testing]] 7 | [com.grzm.awyeah.endpoint :as endpoint])) 8 | 9 | (def endpoints-excerpt 10 | {:partitions 11 | [{:defaults 12 | {:hostname "{service}.{region}.{dnsSuffix}" 13 | :protocols ["https"] 14 | :signatureVersions ["v4"]} 15 | :dnsSuffix "amazonaws.com" 16 | :partition "aws" 17 | :partitionName "AWS Standard" 18 | :regionRegex "^(us|eu|ap|sa|ca)\\-\\w+\\-\\d+$" 19 | :regions 20 | {:us-east-1 {:description "US East (N. Virginia)"}} 21 | :services 22 | {:ec2 23 | {:defaults {:protocols ["http" "https"]} 24 | :endpoints 25 | {:us-east-1 {}}} 26 | :marketplacecommerceanalytics {:endpoints {:us-east-1 {}}} 27 | :iam 28 | {:endpoints 29 | {:aws-global 30 | {:credentialScope {:region "us-east-1"} 31 | :hostname "iam.amazonaws.com"}} 32 | :isRegionalized false 33 | :partitionEndpoint "aws-global"}}}]}) 34 | 35 | (deftest test-resolve-endpoints 36 | (testing "resolves regionalized endpoints" 37 | (with-redefs [endpoint/resolver (constantly endpoints-excerpt)] 38 | (is (= "ec2.us-east-1.amazonaws.com" (:hostname (endpoint/resolve* :ec2 :us-east-1)))))) 39 | (testing "resolves global endpoints" 40 | (with-redefs [endpoint/resolver (constantly endpoints-excerpt)] 41 | (is (= "iam.amazonaws.com" 42 | (:hostname (endpoint/resolve* :iam :us-east-1)))))) 43 | (testing "uses defaults to resolve unspecified endpoints" 44 | (with-redefs [endpoint/resolver (constantly endpoints-excerpt)] 45 | (is (= "i-do-not-exist.us-east-1.amazonaws.com" 46 | (:hostname (endpoint/resolve* :i-do-not-exist :us-east-1))))))) 47 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/generators.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.generators 2 | (:require 3 | [clojure.string :as str] 4 | [clojure.test.check.generators :as gen] 5 | [com.grzm.awyeah.util :as util])) 6 | 7 | ;; see cognitect.aws.protocols.rest/serialize-uri 8 | ;; we want to mimic inputs to cognitect.aws.signers/sign-http-request 9 | (defn ^:private serialize-path-part 10 | [part] 11 | (-> part 12 | (util/url-encode) 13 | (str/replace "%2F" "/") 14 | (str/replace "%7E" "~"))) 15 | 16 | (defn gen-service 17 | [sig-ver] 18 | (gen/let [service-name (gen/such-that seq gen/string-alphanumeric)] 19 | {:metadata {:signatureVersion sig-ver 20 | :signingName service-name}})) 21 | 22 | (def gen-request 23 | (gen/let [host (gen/fmap #(str % ".com") (gen/such-that seq gen/string-alphanumeric)) 24 | path-parts (gen/vector (gen/fmap serialize-path-part (gen/such-that (complement str/blank?) gen/string-ascii)) 1 10) 25 | path-separator (gen/elements ["/" "//"]) 26 | query-ks (gen/vector (gen/such-that seq gen/string-alphanumeric)) 27 | query-vs (gen/vector (gen/such-that seq gen/string-alphanumeric) (count query-ks)) 28 | method (gen/elements [:get :post]) 29 | ;; https://github.com/aws/aws-sdk-java/blob/d35b018/aws-java-sdk-core/src/main/java/com/amazonaws/auth/internal/SignerKey.java#L30-L34 30 | ;; date must be >1 day past epoch and <= today 31 | ;; 1668999574880 == 2022-11-20 32 | epoch (gen/large-integer* {:min 86400000 :max 1668999574880}) 33 | body gen/string] 34 | {:request-method method 35 | :body (.getBytes ^String body "UTF-8") 36 | :headers {"x-amz-date" (util/format-date util/x-amz-date-format (java.util.Date. epoch)) 37 | "host" host} 38 | :uri (str path-separator 39 | (str/join path-separator path-parts) 40 | (when (seq query-ks) 41 | (str "?" (str/join "&" (map #(str %1 "=" %2) 42 | query-ks 43 | query-vs)))))})) 44 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/integration/error_codes_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.integration.error-codes-test 2 | (:require 3 | [clojure.test :refer [deftest is testing]] 4 | [com.grzm.awyeah.client.api :as aws] 5 | [com.grzm.awyeah.client.api.localstack-elf :as localstack-elf])) 6 | 7 | (defn client [opts] 8 | (aws/client (merge localstack-elf/client-default-opts opts))) 9 | 10 | (deftest ^:integration error-codes-for-protocols 11 | (testing "rest-xml" 12 | (is (= "NoSuchBucket" 13 | (:cognitect.aws.error/code 14 | (aws/invoke (client {:api :s3}) 15 | {:op :GetObject 16 | :request {:Bucket "i-do-not-exist" 17 | :Key "neither-do-i.txt"}}))))) 18 | 19 | (testing "rest-json" 20 | (is (= "ValidationException" 21 | (:cognitect.aws.error/code 22 | (aws/invoke (client {:api :lambda}) 23 | {:op :CreateFunction 24 | :request {:FunctionName "some-function-name" 25 | :Role "not-a-role-arn" 26 | :Code {}}}))))) 27 | 28 | (testing "ec2" 29 | (is (= "MissingParameter" 30 | (:cognitect.aws.error/code 31 | (aws/invoke (client {:api :ec2}) 32 | {:op :CreateLaunchTemplate}))))) 33 | 34 | ;; autoscaling isn't supported in community edition of Localstack 35 | #_(testing "query" 36 | (is (= "ValidationError" 37 | (:cognitect.aws.error/code 38 | (aws/invoke (client {:api :autoscaling}) 39 | {:op :PutScalingPolicy}))))) 40 | 41 | (testing "json" 42 | (is (= "ValidationException" 43 | (:cognitect.aws.error/code 44 | (aws/invoke (client {:api :ssm}) 45 | {:op :PutParameter 46 | :request {:Name "ssm"}})))))) 47 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/integration/s3_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.integration.s3-test 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.test :refer [deftest is testing]] 5 | [com.grzm.awyeah.client.api :as aws] 6 | [com.grzm.awyeah.client.api.localstack-elf :as localstack-elf]) 7 | (:import 8 | (java.nio ByteBuffer) 9 | (java.time Instant))) 10 | 11 | (defn bucket-listed? [list-buckets-response bucket-name] 12 | (contains? (->> list-buckets-response 13 | :Buckets 14 | (map :Name) 15 | (into #{})) 16 | bucket-name)) 17 | 18 | (deftest ^:integration test-s3-client 19 | (let [s3 (aws/client (merge {:api :s3} 20 | localstack-elf/client-default-opts)) 21 | bucket-name (str "aws-api-test-bucket-" (.getEpochSecond (Instant/now)))] 22 | 23 | (testing ":CreateBucket" 24 | (aws/invoke s3 {:op :CreateBucket :request {:Bucket bucket-name}}) 25 | (is (bucket-listed? (aws/invoke s3 {:op :ListBuckets}) bucket-name))) 26 | 27 | (testing ":PutObject with byte-array" 28 | (aws/invoke s3 {:op :PutObject :request {:Bucket bucket-name 29 | :Key "hello.txt" 30 | :Body (.getBytes "Hello!")}}) 31 | (is (= "Hello!" (->> (aws/invoke s3 {:op :GetObject 32 | :request {:Bucket bucket-name 33 | :Key "hello.txt"}}) 34 | :Body 35 | slurp)))) 36 | 37 | (testing ":PutObject with input-stream" 38 | (aws/invoke s3 {:op :PutObject :request {:Bucket bucket-name 39 | :Key "hai.txt" 40 | :Body (io/input-stream (.getBytes "Oh hai!"))}}) 41 | (is (= "Oh hai!" (->> (aws/invoke s3 {:op :GetObject 42 | :request {:Bucket bucket-name 43 | :Key "hai.txt"}}) 44 | :Body 45 | slurp)))) 46 | 47 | (testing ":PutObject with ByteBuffer" 48 | (aws/invoke s3 {:op :PutObject :request {:Bucket bucket-name 49 | :Key "oi.txt" 50 | :Body (ByteBuffer/wrap (.getBytes "Oi!"))}}) 51 | (is (= "Oi!" (->> (aws/invoke s3 {:op :GetObject 52 | :request {:Bucket bucket-name 53 | :Key "oi.txt"}}) 54 | :Body 55 | slurp)))) 56 | 57 | (testing ":DeleteObjects and :DeleteBucket" 58 | (aws/invoke s3 {:op :DeleteObjects :request {:Bucket bucket-name 59 | :Delete {:Objects [{:Key "hello.txt"} 60 | {:Key "hai.txt"} 61 | {:Key "oi.txt"}]}}}) 62 | (aws/invoke s3 {:op :DeleteBucket :request {:Bucket bucket-name}}) 63 | (is (not (bucket-listed? (aws/invoke s3 {:op :ListBuckets}) bucket-name)))))) 64 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/interceptors_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.interceptors-test 5 | (:require 6 | [clojure.test :refer [deftest is]] 7 | [com.grzm.awyeah.interceptors :as interceptors])) 8 | 9 | (deftest test-apigatewaymanagementapi 10 | (is (= "prefixsuffix" 11 | (:uri 12 | (interceptors/modify-http-request {:metadata {:uid "apigatewaymanagementapi"}} 13 | {:op :PostToConnection 14 | :request {:ConnectionId "suffix"}} 15 | {:uri "prefix"}))))) 16 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/jdk.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.jdk 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as str] 5 | [com.grzm.awyeah.util :as util]) 6 | (:import 7 | (java.net URI) 8 | (com.amazonaws DefaultRequest) 9 | (com.amazonaws.auth AWS4Signer) 10 | (com.amazonaws.auth BasicAWSCredentials) 11 | (com.amazonaws.http HttpMethodName) 12 | (com.amazonaws.services.s3.internal AWSS3V4Signer) 13 | (com.amazonaws.services.s3.request S3HandlerContextKeys))) 14 | 15 | (defn ^:private ->http-method 16 | [request] 17 | (HttpMethodName/fromValue (name (:request-method request)))) 18 | 19 | (defn ^:private ->x-amz-date 20 | [request] 21 | (->> (get-in request [:headers "x-amz-date"]) 22 | (util/parse-date util/x-amz-date-format))) 23 | 24 | (defn ^:private ->basic-credentials 25 | [credentials] 26 | (BasicAWSCredentials. (:aws/access-key-id credentials) 27 | (:aws/secret-access-key credentials))) 28 | 29 | (defn ^:private ->default-request 30 | [service request] 31 | (let [[path query] (str/split (:uri request) #"\?" 2) 32 | req (doto (DefaultRequest. (get-in service [:metadata :signingName])) 33 | (.setHttpMethod (->http-method request)) 34 | (.setContent (io/input-stream (:body request))) 35 | (.setEndpoint (URI. (str "https://" (get-in request [:headers "host"])))) 36 | (.setResourcePath (str/replace path #"^//+" "/")))] 37 | (when (seq query) 38 | (doseq [[k v] (map #(str/split % #"=") (str/split query #"&"))] 39 | (.addParameter req k v))) 40 | req)) 41 | 42 | (defn v4-jdk-signed-request 43 | [service credentials request] 44 | (let [basic-credentials (->basic-credentials credentials) 45 | req (->default-request service request) 46 | signer (doto (AWS4Signer.) 47 | (.setServiceName (get-in service [:metadata :signingName])) 48 | (.setOverrideDate (->x-amz-date request)))] 49 | (.sign signer req basic-credentials) 50 | req)) 51 | 52 | (defn s3v4-jdk-signed-request 53 | [service credentials request] 54 | (let [basic-credentials (->basic-credentials credentials) 55 | req (doto (->default-request service request) 56 | (.addHandlerContext S3HandlerContextKeys/IS_PAYLOAD_SIGNING_ENABLED true) 57 | (.addHandlerContext S3HandlerContextKeys/IS_CHUNKED_ENCODING_DISABLED true)) 58 | signer (doto (AWSS3V4Signer.) 59 | (.setServiceName (get-in service [:metadata :signingName])) 60 | (.setOverrideDate (->x-amz-date request)))] 61 | (.sign signer req basic-credentials) 62 | req)) 63 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/protocols/rest_test.clj: -------------------------------------------------------------------------------- 1 | (ns com.grzm.awyeah.protocols.rest-test 2 | (:require 3 | [clojure.test :refer [deftest is testing]] 4 | [com.grzm.awyeah.protocols.rest :as protocols.rest])) 5 | 6 | (deftest test-serialize-url 7 | (testing "ensures no double-slash" 8 | (is (= "/a/b/c/d/e/f" 9 | (protocols.rest/serialize-uri "/{Foo+}/{Bar+}" {} {:Foo "a/b/c" :Bar "d/e/f"}) 10 | (protocols.rest/serialize-uri "/{Foo+}/{Bar+}" {} {:Foo "a/b/c" :Bar "/d/e/f"}) 11 | (protocols.rest/serialize-uri "/{Foo+}/{Bar+}" {} {:Foo "/a/b/c" :Bar "/d/e/f"}) 12 | (protocols.rest/serialize-uri "/{Foo+}/{Bar+}" {} {:Foo "/a/b/c" :Bar "d/e/f"})))) 13 | (testing "throws when required key is missing" 14 | (is (thrown-with-msg? Exception 15 | #"missing" 16 | (protocols.rest/serialize-uri "/{Bucket}" {:required ["Bucket"]} {}))) 17 | (is (thrown-with-msg? Exception 18 | #"missing" 19 | (protocols.rest/serialize-uri "/{Bucket}" {:required ["Bucket"]} {:BucketName "wrong key"}))) 20 | (is (thrown-with-msg? Exception 21 | #"missing" 22 | (protocols.rest/serialize-uri "/{Bucket}/{Key+}" {:required ["Bucket" "Key"]} {:Bucket "foo"})))) 23 | (testing "serializes longs" 24 | (is (= "/a/1/b" 25 | (protocols.rest/serialize-uri "/a/{count}/b" {:type "structure" 26 | :members {:Count {:shape "__long", :location "uri", :locationName "count"}} 27 | :required ["Count"]} 28 | {:count 1}))))) 29 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/region_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.region-test 5 | (:require 6 | [clojure.core.async :as a] 7 | [clojure.java.io :as io] 8 | [clojure.test :refer [deftest is testing use-fixtures]] 9 | [com.grzm.awyeah.ec2-metadata-utils :as ec2-metadata-utils] 10 | [com.grzm.awyeah.ec2-metadata-utils-test :as ec2-metadata-utils-test] 11 | [com.grzm.awyeah.region :as region] 12 | [com.grzm.awyeah.test.utils :as tu] 13 | [com.grzm.awyeah.util :as util])) 14 | 15 | (use-fixtures :once ec2-metadata-utils-test/test-server) 16 | 17 | (deftest chain-region-provider-test 18 | (let [r "us-east-1" 19 | p1 (reify region/RegionProvider (region/fetch [_])) 20 | p2 (reify region/RegionProvider (region/fetch [_] r))] 21 | (testing "provider calls each provider until one returns a region" 22 | (is (= r (region/fetch (region/chain-region-provider [p1 p2]))))) 23 | (testing "provider throws if none of the providers returns a region." 24 | (is (thrown-with-msg? Exception #"No region found" (region/fetch (region/chain-region-provider [p1]))))))) 25 | 26 | (deftest profile-region-provider-test 27 | (let [config (io/file (io/resource ".aws/config"))] 28 | (testing "reads the default profile correctly." 29 | (is (= "us-east-1" 30 | (region/fetch (region/profile-region-provider "default" config))))) 31 | (testing "reads a custom profile correctly." 32 | (is (= "us-west-1" 33 | (region/fetch (region/profile-region-provider "tardigrade" config))))) 34 | 35 | (testing "uses env vars and sys props for credentials file location and profile" 36 | (with-redefs [util/getenv (tu/stub-getenv {"AWS_CONFIG_FILE" config})] 37 | (is (= "us-east-1" (region/fetch (region/profile-region-provider))))) 38 | (with-redefs [util/getenv (tu/stub-getenv {"AWS_CONFIG_FILE" config 39 | "AWS_PROFILE" "tardigrade"})] 40 | (is (= "us-west-1" (region/fetch (region/profile-region-provider))))) 41 | (with-redefs [util/getenv (tu/stub-getenv {"AWS_CONFIG_FILE" config}) 42 | util/getProperty (tu/stub-getProperty {"aws.profile" "tardigrade"})] 43 | (is (= "us-west-1" (region/fetch (region/profile-region-provider)))))))) 44 | 45 | (deftest instance-region-provider-test 46 | (testing "provider caches the fetched value" 47 | (let [orig-get-region-fn ec2-metadata-utils/get-ec2-instance-region 48 | request-counter (atom 0) 49 | fetch-counter (atom 0)] 50 | (with-redefs [ec2-metadata-utils/get-ec2-instance-region 51 | (fn [http] 52 | (swap! fetch-counter inc) 53 | (orig-get-region-fn http))] 54 | (let [num-requests 10 55 | p (region/instance-region-provider ec2-metadata-utils-test/*http-client*) 56 | chans (repeatedly num-requests 57 | #(do 58 | (swap! request-counter inc) 59 | (region/fetch-async p)))] 60 | (is (apply = "us-east-1" (map #(a/!! c {:this :map}) 14 | response-ch (retry/with-retry 15 | (constantly c) 16 | (a/promise-chan) 17 | (constantly false) 18 | (constantly nil))] 19 | (a/!! c {:this :map}) 26 | response-ch (retry/with-retry 27 | (constantly c) 28 | (a/promise-chan) 29 | (constantly true) 30 | (constantly nil))] 31 | (a/!! c {:cognitect.anomalies/category :cognitect.anomalies/busy 37 | :test/attempt-number 1}) 38 | (a/>!! c {:cognitect.anomalies/category :cognitect.anomalies/busy 39 | :test/attempt-number 2}) 40 | (a/>!! c {:cognitect.anomalies/category :cognitect.anomalies/busy 41 | :test/attempt-number 3}) 42 | (a/!! c {:cognitect.anomalies/category :cognitect.anomalies/unavailable 52 | :test/attempt-number 1}) 53 | (a/>!! c {:cognitect.anomalies/category :cognitect.anomalies/unavailable 54 | :test/attempt-number 2}) 55 | (a/>!! c {:cognitect.anomalies/category :cognitect.anomalies/unavailable 56 | :test/attempt-number 3}) 57 | (a/!! c {:cognitect.anomalies/category :cognitect.anomalies/busy 67 | :test/attempt-number 1}) 68 | (a/>!! c {:cognitect.anomalies/category :cognitect.anomalies/busy 69 | :test/attempt-number 2}) 70 | (a/>!! c {:test/attempt-number 3}) 71 | (a/> ((juxt :fail :error) res) 38 | (some #(pos? %))) 39 | (System/exit 1))))) 40 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/test/ec2_metadata_utils_server.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.test.ec2-metadata-utils-server 5 | "Modeled after com.amazonaws.util.EC2MetadataUtilsServer" 6 | (:require 7 | [com.grzm.awyeah.ec2-metadata-utils :as ec2-metadata-utils] 8 | [com.grzm.awyeah.json :as json] 9 | [com.grzm.awyeah.util :as u] 10 | [org.httpkit.server :as http-server])) 11 | 12 | (def iam-info 13 | (json/write-str 14 | {"Code" "Success" 15 | "LastUpdated" "2014-04-07T08 18 41Z" 16 | "InstanceProfileArn" "foobar" 17 | "InstanceProfileId" "moobily" 18 | "NewFeature" 12345})) 19 | 20 | (def iam-cred-list 21 | "test1\ntest2") 22 | 23 | (def iam-cred 24 | (json/write-str 25 | {"Code" "Success" 26 | "LastUpdated" "2014-04-07T08:18:41Z" 27 | "Type" "AWS-HMAC" 28 | "AccessKeyId" "foobar" 29 | "SecretAccessKey" "it^s4$3cret!" 30 | "Token" "norealvalue" 31 | "Expiration" "2014-04-08T23:16:53Z"})) 32 | 33 | (def instance-info 34 | (json/write-str 35 | {"pendingTime" "2014-08-07T22:07:46Z" 36 | "instanceType" "m1.small" 37 | "imageId" "ami-a49665cc" 38 | "instanceId" "i-6b2de041" 39 | "billingProducts" ["foo"] 40 | "architecture" "x86_64" 41 | "accountId" "599169622985" 42 | "kernelId" "aki-919dcaf8" 43 | "ramdiskId" "baz" 44 | "region" "us-east-1" 45 | "version" "2010-08-31" 46 | "availabilityZone" "us-east-1b" 47 | "privateIp" "10.201.215.38" 48 | "devpayProductCodes" ["bar"]})) 49 | 50 | (defn route 51 | [uri] 52 | (cond 53 | (= uri "/latest/meta-data/iam/info") iam-info 54 | (or (u/getenv ec2-metadata-utils/container-credentials-relative-uri-env-var) 55 | (u/getenv ec2-metadata-utils/container-credentials-full-uri-env-var)) iam-cred 56 | (= uri "/latest/meta-data/iam/security-credentials/") iam-cred-list 57 | (re-find #"/latest/meta-data/iam/security-credentials/.+" uri) iam-cred 58 | (= uri "/latest/dynamic/instance-identity/document") instance-info 59 | :else nil)) 60 | 61 | (defn handler 62 | [req] 63 | (let [resp-body (route (:uri req))] 64 | {:status (if resp-body 200 404) 65 | :body resp-body})) 66 | 67 | (defn start 68 | "Starts a ec2 metadata utils server. Returns a no-arg stop function." 69 | [port] 70 | (http-server/run-server handler {:ip "127.0.0.1" :port port})) 71 | 72 | (comment 73 | (def stop-fn (start 0)) 74 | 75 | (stop-fn)) 76 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/test/utils.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.test.utils) 5 | 6 | (defn stub-getenv [env] 7 | (fn 8 | ([] env) 9 | ([k] (get env k)))) 10 | 11 | (defn stub-getProperty [props] 12 | (fn [k] 13 | (get props k))) 14 | -------------------------------------------------------------------------------- /test/src/com/grzm/awyeah/util_test.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Cognitect, Inc. 2 | ;; All rights reserved. 3 | 4 | (ns com.grzm.awyeah.util-test 5 | (:require 6 | [clojure.test :as t :refer [deftest is testing]] 7 | [clojure.java.io :as io] 8 | [com.grzm.awyeah.util :as util]) 9 | (:import 10 | (java.nio ByteBuffer) 11 | (java.util Arrays 12 | Random))) 13 | 14 | (deftest test-input-stream->byte-array 15 | (is (= "hi" (slurp (util/input-stream->byte-array (io/input-stream (.getBytes "hi")))))) 16 | (testing "works with a 1mb array" 17 | (let [input (byte-array (int (Math/pow 2 20))) 18 | rng (Random.) 19 | _ (.nextBytes rng input) 20 | output (util/input-stream->byte-array (io/input-stream input))] 21 | (is (Arrays/equals ^bytes input ^bytes output))))) 22 | 23 | (deftest test-bbuff->byte-array 24 | (testing "return ByteBuffer starting at the current position" 25 | (let [bb (ByteBuffer/wrap (.getBytes "bye")) 26 | positioned-bb (-> (.getBytes "hi bye") 27 | ByteBuffer/wrap 28 | (.position 3)) 29 | direct-bb (-> (ByteBuffer/allocateDirect 10) 30 | (.put (ByteBuffer/wrap (.getBytes "bye"))) 31 | .flip)] 32 | (is (= (seq (util/bbuff->byte-array bb)) 33 | (seq (util/bbuff->byte-array positioned-bb)) 34 | (seq (util/bbuff->byte-array direct-bb))))))) 35 | 36 | (deftest test-sha-256 37 | (testing "returns sha for empty string if given nil" 38 | (is (= (seq (util/sha-256 nil)) 39 | (seq (util/sha-256 "")) 40 | (seq (util/sha-256 (.getBytes "")))))) 41 | (testing "accepts string, byte array, or ByteBuffer" 42 | (is (= (seq (util/sha-256 "hi")) 43 | (seq (util/sha-256 (.getBytes "hi"))) 44 | (seq (util/sha-256 (ByteBuffer/wrap (.getBytes "hi"))))))) 45 | (testing "does not consume a ByteBuffer" 46 | (let [bb (ByteBuffer/wrap (.getBytes "hi"))] 47 | (util/sha-256 bb) 48 | (is (= "hi" (util/bbuf->str bb))))) 49 | (testing "return ByteBuffer content starting at current the position" 50 | (let [bb (ByteBuffer/wrap (.getBytes "bye")) 51 | positioned-bb (-> (.getBytes "hi bye") 52 | ByteBuffer/wrap 53 | (.position 3)) 54 | direct-bb (-> (ByteBuffer/allocateDirect 10) 55 | (.put (ByteBuffer/wrap (.getBytes "bye"))) 56 | .flip)] 57 | (is (= (seq (util/sha-256 bb)) 58 | (seq (util/sha-256 positioned-bb)) 59 | (seq (util/sha-256 direct-bb))))))) 60 | 61 | (deftest test-md5 62 | (testing "Respects positioned ByteBuffer arrays and direct ByteBuffers" 63 | (let [bb (ByteBuffer/wrap (.getBytes "bye")) 64 | positioned-bb (-> (.getBytes "hi bye") 65 | ByteBuffer/wrap 66 | (.position 3)) 67 | direct-bb (-> (ByteBuffer/allocateDirect 10) 68 | (.put (ByteBuffer/wrap (.getBytes "bye"))) 69 | .flip)] 70 | (is (= (seq (util/md5 bb)) 71 | (seq (util/md5 positioned-bb)) 72 | (seq (util/md5 direct-bb))))))) 73 | 74 | (deftest test-xml-read 75 | (testing "removes whitespace-only nodes, preserving whitespace in single text nodes" 76 | (let [parsed (util/xml-read " 77 | outer-value 78 | 79 | inner-value 80 | 81 | ")] 82 | (is (= 2 (count (-> parsed :content)))) 83 | (is (re-matches #"\n\s+outer-value\s+" (-> parsed :content first))) 84 | (is (= 1 (count (-> parsed :content last :content)))) 85 | (is (re-matches #"\n\s+inner-value\s+" (-> parsed :content last :content first)))))) 86 | 87 | (deftest hex-encode 88 | (is (= (util/hex-encode (byte-array (range -128 128))) 89 | "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"))) 90 | 91 | (comment 92 | (t/run-tests)) 93 | --------------------------------------------------------------------------------