diff --git a/.github/workflows/gen-docs.yml b/.github/workflows/gen-docs.yml index b29e60bc7..d4ac6dcdf 100644 --- a/.github/workflows/gen-docs.yml +++ b/.github/workflows/gen-docs.yml @@ -12,14 +12,19 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v3 + with: + fetch-depth: 0 - name: Install Dependencies run: | - brew install sourcekitten - gem install jazzy + brew install mint + mint install apple/swift-docc@main + echo "$HOME/.mint/bin" >> $GITHUB_PATH - name: Build + env: + DOCC: docc run: | - ./scripts/build-docs.sh + ./scripts/build-docc.sh - name: Commit env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -29,9 +34,9 @@ jobs: git config user.name "${GITHUB_ACTOR}" git config user.email "${GITHUB_ACTOR}@users.noreply.github.com" git checkout ${REMOTE_BRANCH} - rm -rf 5.x.x/ - mv docs/ 5.x.x/ - git add --all 5.x.x + rm -rf 6.x.x/ + mv docs/soto-core/6.x.x/ 6.x.x/ + git add --all 6.x.x git status git commit -m "Documentation for https://github.com/${GITHUB_REPOSITORY}/tree/${GITHUB_SHA}" -m "Generated by gen-docs.yml" diff --git a/SotoCore.docc/SotoCore/AWSClient.md b/SotoCore.docc/SotoCore/AWSClient.md new file mode 100644 index 000000000..0266e3373 --- /dev/null +++ b/SotoCore.docc/SotoCore/AWSClient.md @@ -0,0 +1,151 @@ +# ``SotoCore/AWSClient`` + +@Metadata { + @DocumentationExtension(mergeBehavior: override) +} + +Client managing communication with AWS services + +## Overview + +The `AWSClient` is the core of Soto. This is the object that manages your communication with AWS. It manages credential acquisition, takes your request, encodes it, signs it, sends it to AWS and then decodes the response for you. In most situations your application should only require one `AWSClient`. Create this at startup and use it throughout. + +When creating an `AWSClient` you need to provide how you are going to acquire AWS credentials, what your policy is on retrying failed requests, a list of middleware you would apply to requests to AWS and responses from AWS, client options, where you get your `HTTPClient` and a `Logger` to log any output not directly linked to a request. There are defaults for most of these parameters. The only one required is the `httpClientProvider`. + +```swift +let awsClient = AWSClient( + credentialProvider: .default, + retryPolicy: .default, + middlewares: [], + options: .init(), + httpClientProvider: .createNew, + logger: AWSClient.loggingDisabled +) +``` + +### Credential Provider + +The `credentialProvider` defines how the client acquires its AWS credentials. You provide `AWSClient.init` with a factory function that will create the credential provider once the `AWSClient` has been initialised. This allows for the credential provider to use the `EventLoopGroup`, `Logger` and `HTTPClient` that the `AWSClient` uses. You can find factory functions for all the standard credential providers in ``CredentialProviderFactory``. + +```swift +let awsClient = AWSClient( + credentialProvider: .environment, + ... +) +``` + +If no credential provider is provided the default is to try environment variables, ECS container credentials, EC2 instance metadata and then the `~/.aws/credentials` file in this order. The first method that is successful will be used. + +An alternative is to provide credentials in code. You can do this as follows + +```swift +let client = AWSClient( + credentialProvider: .static( + accessKeyId: "MY_AWS_ACCESS_KEY_ID", + secretAccessKey: "MY_AWS_SECRET_ACCESS_KEY" + ), + ... +) +``` +The article gives more information on credential providers. + +### Retry policy + +The `retryPolicy` defines how the client reacts to a failed request. There are three retry policies supplied. `.noRetry` doesn't retry the request if it fails. The other two will retry if the response is a 5xx (server error) or a connection error. They differ in how long they wait before performing the retry. `.exponential` doubles the wait time after each retry and `.jitter` is the same as exponential except it adds a random element to the wait time. `.jitter` is the recommended method from AWS so it is the default. + +### Middleware + +Middleware allows you to insert your own code just as a request has been constructed or a response has been received. You can use this to edit the request/response or just to view it. SotoCore supplies one middleware — `AWSLoggingMiddleware` — which outputs your request to the console once constructed and the response is received from AWS. + +### HTTP Client provider + +The `HTTPClientProvider` defines where you get your HTTP client from. You have three options: + +- Pass `.createNew` which indicates the `AWSClient` should create its own HTTP client. This creates an instance of `HTTPClient` using [`AsyncHTTPClient`](https://github.com/swift-server/async-http.client). +- Supply your own `EventLoopGroup` with `.createNewWithEventLoopGroup(EventLoopGroup`). This creates a new `HTTPClient` but has it use the supplied `EventLoopGroup`. +- Supply your own HTTP client with `.shared(AWSHTTPClient)` as long as it conforms to the protocol `AWSHTTPClient`. `AsyncHTTPClient.HTTPClient` already conforms to this protocol. + +There are a number of reasons you might want to provide your own client, such as: + +- You have one HTTP client you want to use across all your systems. +- You want to change the configuration for the HTTP client used, perhaps you are running behind a proxy or want to enable response decompression. + +## AWSClient Shutdown + +The AWSClient requires you shut it down manually before it is deinitialized. The manual shutdown is required to ensure any internal processes are finished before the `AWSClient` is freed and Soto's event loops and client are shutdown properly. You can either do this asynchronously with `AWSClient.shutdown()` or do this synchronously with `AWSClient.syncShutdown()`. + +## Topics + +### Initializers + +- ``init(credentialProvider:retryPolicy:middlewares:options:httpClientProvider:logger:)`` +- ``HTTPClientProvider`` +- ``Options`` +- ``loggingDisabled`` + +### Instance Properties + +- ``credentialProvider`` +- ``middlewares`` +- ``retryPolicy`` +- ``httpClient`` +- ``eventLoopGroup`` + +### Shutdown + +- ``shutdown()`` +- ``shutdown(queue:_:)`` +- ``syncShutdown()`` + +### Credentials + +- ``getCredential(on:logger:)-96cyr`` +- ``getCredential(on:logger:)-5dlty`` +- ``signHeaders(url:httpMethod:headers:body:serviceConfig:logger:)-2uyw9`` +- ``signHeaders(url:httpMethod:headers:body:serviceConfig:logger:)-8h5yq`` +- ``signURL(url:httpMethod:headers:expires:serviceConfig:logger:)-8d5k0`` +- ``signURL(url:httpMethod:headers:expires:serviceConfig:logger:)-49aa3`` + +### Errors + +- ``ClientError`` + +### Request Execution + +- ``execute(operation:path:httpMethod:serviceConfig:logger:on:)-6jc01`` +- ``execute(operation:path:httpMethod:serviceConfig:logger:on:)-3mu6q`` +- ``execute(operation:path:httpMethod:serviceConfig:logger:on:)-6hhlh`` +- ``execute(operation:path:httpMethod:serviceConfig:logger:on:)-6klm4`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:logger:on:)-4iuwj`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:logger:on:)-1upt6`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:logger:on:)-3ttl7`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:logger:on:)-3dlpq`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:logger:on:stream:)-3c73e`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:logger:on:stream:)-3yiw5`` +- ``execute(operation:path:httpMethod:serviceConfig:endpointDiscovery:logger:on:)-3ek2x`` +- ``execute(operation:path:httpMethod:serviceConfig:endpointDiscovery:logger:on:)-4uver`` +- ``execute(operation:path:httpMethod:serviceConfig:endpointDiscovery:logger:on:)-9pukf`` +- ``execute(operation:path:httpMethod:serviceConfig:endpointDiscovery:logger:on:)-7gnt3`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:endpointDiscovery:logger:on:)-3hzuw`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:endpointDiscovery:logger:on:)-2l7fr`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:endpointDiscovery:logger:on:)-1vx0e`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:endpointDiscovery:logger:on:)-3mdnx`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:endpointDiscovery:logger:on:stream:)-30x0f`` +- ``execute(operation:path:httpMethod:serviceConfig:input:hostPrefix:endpointDiscovery:logger:on:stream:)-1m1h2`` + +### Pagination + +- ``PaginatorSequence`` +- ``paginate(input:command:inputKey:outputKey:logger:on:onPage:)`` +- ``paginate(input:initialValue:command:inputKey:outputKey:logger:on:onPage:)`` +- ``paginate(input:command:tokenKey:logger:on:onPage:)`` +- ``paginate(input:initialValue:command:tokenKey:logger:on:onPage:)`` +- ``paginate(input:command:tokenKey:moreResultsKey:logger:on:onPage:)`` +- ``paginate(input:initialValue:command:tokenKey:moreResultsKey:logger:on:onPage:)`` + +### Waiters + +- ``waitUntil(_:waiter:maxWaitTime:logger:on:)-3ccn1`` +- ``waitUntil(_:waiter:maxWaitTime:logger:on:)-385eg`` +- ``Waiter`` +- ``WaiterState`` diff --git a/SotoCore.docc/SotoCore/Articles/CredentialProviders.md b/SotoCore.docc/SotoCore/Articles/CredentialProviders.md new file mode 100644 index 000000000..4f5f291c9 --- /dev/null +++ b/SotoCore.docc/SotoCore/Articles/CredentialProviders.md @@ -0,0 +1,102 @@ +# Credential Providers + +Providing Credentials for signing AWS requests + +Before using Soto, you will need AWS credentials to sign all your requests. The main client object, `AWSClient`, accepts a `credentialProvider` parameter in its `init`. With this you can specify how the client should find AWS credentials. The default if you don't set the `credentialProvider` parameter is to select a method from the four methods listed below. Each method is tested in the order they are listed below and the first that is successful is chosen. If you are running on a Mac it ignores the ECS or EC2 methods as they would obviously fail. + +### Load Credentials from Environment Variable + +You can set the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` and `AWSClient` will automatically pick up the credentials from these variables. + +```bash +AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID +AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY +``` + +### Via ECS Container credentials + +If you are running your code as an AWS ECS container task, you can [setup an IAM role](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html#create_task_iam_policy_and_role) for your container task to automatically grant credentials via the metadata service. + +### Via EC2 Instance Profile + +If you are running your code on an AWS EC2 instance, you can [setup an IAM role](https://docs.aws.amazon.com/codedeploy/latest/userguide/getting-started-create-iam-instance-profile.html) as the server's Instance Profile to automatically grant credentials via the metadata service. + +### Load Credentials from shared credential file. + +You can [set shared credentials](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/create-shared-credentials-file.html) in the home directory for the user running the app, in the file `~/.aws/credentials`. + +```ini +[default] +aws_access_key_id = YOUR_AWS_ACCESS_KEY_ID +aws_secret_access_key = YOUR_AWS_SECRET_ACCESS_KEY +``` + +## Pass Credentials to AWSClient directly + +If you would prefer to pass the credentials to the `AWSClient` directly you can specify credentials with the `.static` credential provider as follows. + +```swift +let client = AWSClient( + credentialProvider: .static( + accessKeyId: "MY_AWS_ACCESS_KEY_ID", + secretAccessKey: "MY_AWS_SECRET_ACCESS_KEY" + ) +) +``` +## Without Credentials + +Some services like CognitoIdentityProvider don't require credentials to access some of their functionality. In this case you should use the `.empty` credential provider. This will disable all other credential access functions and send requests unsigned. +```swift +let client = AWSClient(credentialProvider: .empty) +``` + +## Selector Credential Providers + +You can supply a list of credential providers you would like your `AWSClient` to use with the `.selector` credential provider. Each provider in the list is tested, until it finds a provider that successfully provides credentials. The following would test if credentials are available via environment variables, and then in the shared config file `~/.aws/credentials`. +```swift +let client = AWSClient(credentialProvider: .selector(.environment, .configfile())) +``` + +The default credential provider is implemented as a selector. +```swift +.selector(.environment, .ecs, .ec2, .configfile()) +``` + +## STS and Cognito Identity + +The `CredentialProviders` protocol allows you to define credential providers external to the core library. The STS(Security Token Service) and Cognito Identity libraries both provide credential providers. + +STS extends `CredentialProviderFactory` with five new `CredentialProviders`. + +- `stsAssumeRole` for returning temporary credentials for a different role. +- `stsSAML` for users authenticated via a SAML authentication response. +- `stsWebIdentity` for users who have been authenticated in a mobile or web application with a web identity provider. +- `stsWebIdentityTokenFile` for authenticating on EKS clusters. Set `AWS_WEB_IDENTITY_TOKEN_FILE`, `AWS_ROLE_ARN` and `AWS_ROLE_SESSION_NAME` environment variables. +- `federationToken` for providing temporary credentials to federated users. +- `sessionToken` for providing temporary credentials for the provided user with possible MFA authentication. + +See the AWS documentation on [requesting temporary security credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_temp.html). + +CognitoIdentity adds two versions of `cognitoIdentity` for users in a Cognito Identity Pool. The first version takes static identity details and the second you provide an object conforming to `IdentityProvider` which will be used to provide identity details when they are required. Identities can be users in a Cognito User Pool or users who authenticate with external providers such as Facebook, Google and Apple. See the AWS documentation on [Cognito Identity Pools](https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-identity.html). + +For example, to use `STS.AssumeRole` to acquire new credentials you provide a request structure, credential provider to access original credentials and a region to run the STS commands in: + +```swift +import STS + +let request = STS.AssumeRoleRequest(roleArn: "arn:aws:iam::000000000000:role/Admin", roleSessionName: "session-name") +let client = AWSClient(credentialProvider: .stsAssumeRole(request: request, credentialProvider: .ec2, region: .euwest1)) +``` + +Similarly you can setup a Cognito Identity credential provider as follows: + +```swift +import CognitoIdentity + +let credentialProvider: CredentialProviderFactory = .cognitoIdentity( + identityPoolId: poolId, + logins: ["appleid.apple.com": "APPLETOKEN"], + region: .useast1 +) +let client = AWSClient(credentialProvider: credentialProvider) +``` diff --git a/SotoCore.docc/SotoCore/Articles/ServiceObjects.md b/SotoCore.docc/SotoCore/Articles/ServiceObjects.md new file mode 100644 index 000000000..94bb2b06f --- /dev/null +++ b/SotoCore.docc/SotoCore/Articles/ServiceObjects.md @@ -0,0 +1,75 @@ +# AWS Service Objects + +Defining an AWS service. + +In Soto each AWS Service has a service object. This object brings together an `AWSClient` and a service configuration, `AWSServiceConfig`, and provides methods for accessing all the operations available from that service. + +## Initialisation + +The `init` for each service is as follows. Details about each parameter are available below. + +```swift +public init( + client: AWSClient, + region: SotoCore.Region? = nil, + partition: AWSPartition = .aws, + endpoint: String? = nil, + timeout: TimeAmount? = nil, + byteBufferAllocator: ByteBufferAllocator = ByteBufferAllocator(), + options: AWSServiceConfig.Options = [] +) +``` + +#### Client + +The client is the `AWSClient` this service object will use when communicating with AWS. + +#### Region and Partition + +The `region` defines which AWS region you want to use for that service. The `partition` defines which set of AWS server regions you want to work with. Partitions include the standard `.aws`, US government `.awsusgov` and China `.awscn`. If you provide a `region`, the `partition` parameter is ignored. If you don't supply a `region` then the `region` will be set as the default region for the specified `partition`, if that is not defined it will check the `AWS_DEFAULT_REGION` environment variable or default to `us-east-1`. + +Some services do not have a `region` parameter in their initializer, such as IAM. These services require you to communicate with one global region which is defined by the service. You can still control which partition you connect to though. + +#### Endpoint + +If you want to communicate with non-AWS servers you can provide an endpoint which replaces the `amazonaws.com` web address. You may want to do this if you are using an AWS mocking service for debugging purposes for example, or you are communicating with a non-AWS service that replicates AWS functionality. + +#### Time out + +Time out defines how long the HTTP client will wait until it cancels a request. This value defaults to 20 seconds. If you are planning on downloading/uploading large objects you should probably increase this value. `AsyncHTTPClient` allows you to set an additional connection timeout value. If you are extending your general timeout, use an `HTTPClient` configured with a shorter connection timeout to avoid waiting for long periods when a connection fails. + +#### ByteBufferAllocator + +During request processing the `AWSClient` will most likely be required to allocate space for `ByteBuffer`s. You can define how these are allocated with the `byteBufferAllocator` parameter. + +#### Options + +A series of flags, that can affect how requests are constructed. The only option available at the moment is `s3ForceVirtualHost`. S3 uses virtual host addressing by default except if you use a custom endpoint. `s3ForceVirtualHost` will force virtual host addressing even when you specify a custom endpoint. + +## AWSService + +All service objects conform to the `AWSService` protocol. This protocol brings along a couple of extra bits of functionality. + +### Presigned URLs + +When a request is made to AWS it has to be signed. This uses your AWS credentials and the contents of the request to create a signature. When the request is sent to the AWS server, the server also creates a version of this signature. If these two signatures match then AWS knows who is making the request and that it can be trusted. If you want to allow your clients to access AWS resources, creating a presigned request to send to your client is a common way to do this. Alternatively you would have to send AWS credentials to the client and these could be abused. + +One of the most common operations where this is used is for uploading an object to S3. Below creates a presigned URL which someone could use to upload a file to S3. +```swift +let signedURL = s3.signURL( + url: URL(string: "https://.s3.us-east-1.amazonaws.com/")!, + httpMethod: .PUT, + expires: .minutes(60) +).wait() +``` + +The function `signURL` returns an `EventLoopFuture` as it is dependent on a credential provider that may not have been resolved yet. In most cases though you are safe to just `wait` on the result as the credentials will be available. + +### Creating new service objects from existing + +It is possible to create a new version of a service object from an already existing one with additional `AWSServiceMiddleware`, an edited `timeOut`, `byteBufferAllocator` or `options` using the `AWSService.with(middlewares:timeout:byteBufferAllocator:options)` function. + +If you are loading a much larger object then usual into S3 and want to extend the `timeout` value for this one operation you can do it as follows. +```swift +s3.with(timeout: .minutes(10)).putObject(request) +``` diff --git a/SotoCore.docc/SotoCore/SotoCore.md b/SotoCore.docc/SotoCore/SotoCore.md new file mode 100644 index 000000000..5311c80af --- /dev/null +++ b/SotoCore.docc/SotoCore/SotoCore.md @@ -0,0 +1,130 @@ +# ``SotoCore`` + +The core framework for Soto, a Swift SDK for AWS + +## Overview + +SotoCore is the underlying driver for executing requests for the Soto Swift SDK for AWS. You will most likely be using this via one of libraries in [Soto](https://github.com/soto-project/soto). Much of the public APIs here you will never need to know as they are used internally by the Soto service libraries. But there are a few objects that are used. + +## Topics + +### Articles + +- +- + +### Client + +- ``AWSClient`` + +### Services + +- ``AWSService`` +- ``AWSServiceConfig`` +- ``ServiceProtocol`` +- ``Region`` +- ``AWSPartition`` + +### Middleware + +- ``AWSServiceMiddleware`` +- ``AWSLoggingMiddleware`` +- ``AWSMiddlewareContext`` + +### Request/Response + +- ``AWSRequest`` +- ``AWSResponse`` +- ``Body`` + +### Credentials + +- ``CredentialProvider`` +- ``AsyncCredentialProvider`` +- ``NullCredentialProvider`` +- ``ExpiringCredential`` +- ``CredentialProviderFactory`` +- ``DeferredCredentialProvider`` +- ``RotatingCredentialProvider`` +- ``RotatingCredential`` +- ``CredentialProviderError`` + +### Retry + +- ``RetryPolicy`` +- ``RetryPolicyFactory`` +- ``RetryStatus`` + +### Endpoints + +- ``AWSEndpoints`` +- ``AWSEndpointStorage`` +- ``AWSEndpointDiscovery`` + +### Errors + +- ``AWSErrorType`` +- ``AWSErrorContext`` +- ``AWSClientError`` +- ``AWSServerError`` +- ``AWSResponseError`` +- ``AWSRawError`` + +### API Input/Outputs + +- ``AWSShape`` +- ``AWSEncodableShape`` +- ``AWSDecodableShape`` +- ``AWSShapeWithPayload`` +- ``AWSShapeOptions`` +- ``AWSPayload`` +- ``AWSBase64Data`` +- ``AWSMemberEncoding`` +- ``AWSPaginateToken`` + +### Waiters + +- ``AWSWaiterMatcher`` +- ``AWSErrorCodeMatcher`` +- ``AWSErrorStatusMatcher`` +- ``AWSSuccessMatcher`` +- ``JMESPathMatcher`` +- ``JMESAllPathMatcher`` +- ``JMESAnyPathMatcher`` + +### Streaming + +- ``StreamReadFunction`` +- ``StreamReaderResult`` +- ``AWSResponseStream`` + +### Encoding/Decoding + +- ``QueryEncoder`` +- ``CustomCoding`` +- ``OptionalCustomCoding`` +- ``CustomCoder`` +- ``CustomDecoder`` +- ``CustomEncoder`` +- ``OptionalCustomCodingWrapper`` +- ``ArrayCoder`` +- ``DictionaryCoder`` +- ``ArrayCoderProperties`` +- ``DictionaryCoderProperties`` +- ``StandardArrayCoder`` +- ``StandardDictionaryCoder`` +- ``StandardArrayCoderProperties`` +- ``StandardDictionaryCoderProperties`` +- ``ISO8601DateCoder`` +- ``HTTPHeaderDateCoder`` +- ``UnixEpochDateCoder`` + +### CRC32 + +- ``soto_crc32(_:bytes:)`` +- ``soto_crc32c(_:bytes:)`` +- ``CRC32`` + +## See Also + +- ``SotoSignerV4`` diff --git a/SotoCore.docc/SotoSignerV4/AWSSigner.md b/SotoCore.docc/SotoSignerV4/AWSSigner.md new file mode 100644 index 000000000..b00ac7ea2 --- /dev/null +++ b/SotoCore.docc/SotoSignerV4/AWSSigner.md @@ -0,0 +1,26 @@ +# ``SotoSignerV4/AWSSigner`` + +## Topics + +### Initializers + +- ``init(credentials:name:region:)`` + +### Instance Properties + +- ``credentials`` +- ``name`` +- ``region`` + +### Signing + +- ``signURL(url:method:headers:body:expires:omitSecurityToken:date:)`` +- ``signHeaders(url:method:headers:body:omitSecurityToken:date:)`` +- ``processURL(url:)`` +- ``BodyData`` + +### Signing streamed data + +- ``startSigningChunks(url:method:headers:date:)`` +- ``signChunk(body:signingData:)`` +- ``ChunkedSigningData`` diff --git a/SotoCore.docc/SotoSignerV4/SotoSignerV4.md b/SotoCore.docc/SotoSignerV4/SotoSignerV4.md new file mode 100644 index 000000000..5ff4e1f64 --- /dev/null +++ b/SotoCore.docc/SotoSignerV4/SotoSignerV4.md @@ -0,0 +1,61 @@ +# ``SotoSignerV4`` + +Sign HTTP requests before sending them to AWS either by generating a signed URL or a set of signed headers. + +## Overview + +### Initialisation + +To create a `AWSSigner` you need a set of credentials, a signing name and a AWS region. + +```swift +let credentials: Credential = StaticCredential( + accessKeyId: "_MYACCESSKEY_", + secretAccessKey: "_MYSECRETACCESSKEY_" +) +let signer = AWSSigner(credentials: credentials, name: "s3", region: "eu-west-1") +``` + +### Signed URLs + +A signed URL includes the signature as query parameter `X-Amz-Signature`. The method `signURL` will create a signed URL. + +```swift +let url = URL(string: "https://my-bucket.s3.eu-west-1.awsamazon.com/file")! +let signedURL = signer.signURL(url: url, method: .GET, expires: .minutes(60)) +``` + +### Signed Headers + +Instead of returning a signed URL you can add an additional `authorization` header which includes the signature. Use the method `signHeaders` to create a set of signed headers which you can use with the rest of your request. + +```swift +let signedHeaders = signer.signHeaders(url: url, method: .GET, headers: headers, body: .byteBuffer(body)) +``` + +### Processing requests + +Some request URLs need to be processed before signing. The signer expects query parameters to be alphabetically sorted and that paths have been percent encoded. You can use `processURL` to do this work for you. + +```swift +let url = URL(string: "https://my-bucket.s3.eu-west-1.awsamazon.com/file")! +let processedURL = signer.processURL(url)! +let signedURL = signer.signURL(url: processedURL, method: .GET, expires: .minutes(60)) +``` + +You can find out more about the AWS signing process [here](https://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html). + +## Topics + +### Signer + +- ``AWSSigner`` + +### Credentials + +- ``Credential`` +- ``StaticCredential`` + +## See Also + +- ``SotoCore`` \ No newline at end of file diff --git a/Sources/SotoCore/AWSClient+EndpointDiscovery.swift b/Sources/SotoCore/AWSClient+EndpointDiscovery.swift index a5f7d8aae..0df056802 100644 --- a/Sources/SotoCore/AWSClient+EndpointDiscovery.swift +++ b/Sources/SotoCore/AWSClient+EndpointDiscovery.swift @@ -188,9 +188,13 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) + /// - serviceConfig: AWS service configuration used in request creation and signing /// - input: Input object - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - hostPrefix: Prefix to append to host name + /// - endpointDiscovery: Endpoint discovery helper + /// - logger: Logger + /// - eventLoop: Optional EventLoop to run everything on + /// - stream: Closure to stream payload response into /// - returns: /// Future containing output object that completes when response is received public func execute( diff --git a/Sources/SotoCore/AWSClient+Paginate.swift b/Sources/SotoCore/AWSClient+Paginate.swift index 50d541546..5493d6f03 100644 --- a/Sources/SotoCore/AWSClient+Paginate.swift +++ b/Sources/SotoCore/AWSClient+Paginate.swift @@ -17,7 +17,7 @@ import NIOCore // MARK: Pagination -/// protocol for all AWSShapes that can be paginated. +/// Protocol for all AWSShapes that can be paginated. /// Adds an initialiser that does a copy but inserts a new integer based pagination token public protocol AWSPaginateToken: AWSShape { associatedtype Token @@ -25,7 +25,9 @@ public protocol AWSPaginateToken: AWSShape { } extension AWSClient { - /// If an AWS command is returning an arbituary sized array sometimes it adds support for paginating this array + /// Execute request with paginated results + /// + /// If an AWS command is returning an arbitrary sized array sometimes it adds support for paginating this array /// ie it will return the array in blocks of a defined size, each block also includes a token which can be used to access /// the next block. This function loads each block and calls a closure with each block as parameter. This function returns /// the result of combining all of these blocks using the given closure, @@ -78,6 +80,8 @@ extension AWSClient { return promise.futureResult } + /// Execute request with paginated results + /// /// If an AWS command is returning an arbituary sized array sometimes it adds support for paginating this array /// ie it will return the array in blocks of a defined size, each block also includes a token which can be used to access /// the next block. This function loads each block and calls a closure with each block as parameter. @@ -104,6 +108,8 @@ extension AWSClient { } } + /// Execute request with paginated results + /// /// If an AWS command is returning an arbituary sized array sometimes it adds support for paginating this array /// ie it will return the array in blocks of a defined size, each block also includes a token which can be used to access /// the next block. This function loads each block and calls a closure with each block as parameter. This function returns @@ -153,6 +159,8 @@ extension AWSClient { return promise.futureResult } + /// Execute request with paginated results + /// /// If an AWS command is returning an arbituary sized array sometimes it adds support for paginating this array /// ie it will return the array in blocks of a defined size, each block also includes a token which can be used to access /// the next block. This function loads each block and calls a closure with each block as parameter. @@ -177,6 +185,8 @@ extension AWSClient { } } + /// Execute request with paginated results + /// /// If an AWS command is returning an arbituary sized array sometimes it adds support for paginating this array /// ie it will return the array in blocks of a defined size, each block also includes a token which can be used to access /// the next block. This function loads each block and calls a closure with each block as parameter. This function returns @@ -229,6 +239,8 @@ extension AWSClient { return promise.futureResult } + /// Execute request with paginated results + /// /// If an AWS command is returning an arbituary sized array sometimes it adds support for paginating this array /// ie it will return the array in blocks of a defined size, each block also includes a token which can be used to access /// the next block. This function loads each block and calls a closure with each block as parameter. diff --git a/Sources/SotoCore/AWSClient.swift b/Sources/SotoCore/AWSClient.swift index cd7fd42e0..e87f4e48d 100644 --- a/Sources/SotoCore/AWSClient.swift +++ b/Sources/SotoCore/AWSClient.swift @@ -31,9 +31,11 @@ import NIOTransportServices import SotoSignerV4 import SotoXML -/// This is the workhorse of SotoCore. You provide it with a `AWSShape` Input object, it converts it to `AWSRequest` which is then converted -/// to a raw `HTTPClient` Request. This is then sent to AWS. When the response from AWS is received if it is successful it is converted to a `AWSResponse` -/// which is then decoded to generate a `AWSShape` Output object. If it is not successful then `AWSClient` will throw an `AWSErrorType`. +/// Client managing communication with AWS services +/// +/// This is the workhorse of SotoCore. You provide it with a ``AWSShape`` Input object, it converts it to ``AWSRequest`` which is then converted +/// to a raw `HTTPClient` Request. This is then sent to AWS. When the response from AWS is received if it is successful it is converted to a ``AWSResponse`` +/// which is then decoded to generate a ``AWSShape`` Output object. If it is not successful then `AWSClient` will throw an ``AWSErrorType``. public final class AWSClient { // MARK: Member variables @@ -66,8 +68,10 @@ public final class AWSClient { /// Initialize an AWSClient struct /// - parameters: /// - credentialProvider: An object that returns valid signing credentials for request signing. - /// - retryPolicy: Object returning whether retries should be attempted. Possible options are NoRetry(), ExponentialRetry() or JitterRetry() + /// - retryPolicy: Object returning whether retries should be attempted. + /// Possible options are `.default`, `.noRetry`, `.exponential` or `.jitter`. /// - middlewares: Array of middlewares to apply to requests and responses + /// - options: Configuration flags /// - httpClientProvider: HTTPClient to use. Use `.createNew` if you want the client to manage its own HTTPClient. /// - logger: Logger used to log background AWSClient events public init( @@ -108,7 +112,9 @@ public final class AWSClient { // MARK: API Calls - /// Shutdown client synchronously. Before an `AWSClient` is deleted you need to call this function or the async version `shutdown` + /// Shutdown client synchronously. + /// + /// Before an `AWSClient` is deleted you need to call this function or the async version `shutdown` /// to do a clean shutdown of the client. It cleans up `CredentialProvider` tasks and shuts down the HTTP client if it was created by /// the `AWSClient`. /// @@ -133,7 +139,9 @@ public final class AWSClient { } } - /// Shutdown AWSClient asynchronously. Before an `AWSClient` is deleted you need to call this function or the synchronous + /// Shutdown AWSClient asynchronously. + /// + /// Before an `AWSClient` is deleted you need to call this function or the synchronous /// version `syncShutdown` to do a clean shutdown of the client. It cleans up `CredentialProvider` tasks and shuts down /// the HTTP client if it was created by the `AWSClient`. Given we could be destroying the `EventLoopGroup` the client /// uses, we have to use a `DispatchQueue` to run some of this work on. @@ -198,13 +206,12 @@ public final class AWSClient { /// Specifies how `HTTPClient` will be created and establishes lifecycle ownership. public enum HTTPClientProvider { - /// HTTP Client will be provided by the user. Owner of this group is responsible for its lifecycle. Any HTTPClient that conforms to - /// `AWSHTTPClient` can be specified here including AsyncHTTPClient + /// Use HTTPClient provided by the user. User is responsible for the lifecycle of the HTTPClient. case shared(HTTPClient) - /// HTTP Client will be created by the client using provided EventLoopGroup. When `shutdown` is called, created `HTTPClient` + /// HTTPClient will be created by AWSClient using provided EventLoopGroup. When `shutdown` is called, created `HTTPClient` /// will be shut down as well. case createNewWithEventLoopGroup(EventLoopGroup) - /// HTTP Client will be created by the client. When `shutdown` is called, created `HTTPClient` will be shut down as well. + /// `HTTPClient` will be created by `AWSClient`. When `shutdown` is called, created `HTTPClient` will be shut down as well. case createNew } @@ -236,9 +243,11 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) + /// - serviceConfig: AWS Service configuration /// - input: Input object - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - hostPrefix: String to prefix host name with + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on /// - returns: /// Empty Future that completes when response is received public func execute( @@ -280,8 +289,9 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - serviceConfig: AWS Service configuration + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on /// - returns: /// Empty Future that completes when response is received public func execute( @@ -319,8 +329,9 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - serviceConfig: AWS Service configuration + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on /// - returns: /// Future containing output object that completes when response is received public func execute( @@ -358,9 +369,11 @@ extension AWSClient { /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) + /// - serviceConfig: AWS Service configuration /// - input: Input object - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - hostPrefix: String to prefix host name with + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on /// - returns: /// Future containing output object that completes when response is received public func execute( @@ -397,14 +410,19 @@ extension AWSClient { ) } - /// Execute a request with an input object and return a future with the output object generated from the response + /// Execute a request with an input object and return output object generated from the response + /// + /// This version of execute also streams the payload of the response into the provided closure /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) + /// - serviceConfig: AWS Service configuration /// - input: Input object - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - hostPrefix: String to prefix host name with + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on + /// - stream: Closure to stream payload response into /// - returns: /// Future containing output object that completes when response is received public func execute( diff --git a/Sources/SotoCore/AWSService.swift b/Sources/SotoCore/AWSService.swift index 455d3d0c8..051398937 100644 --- a/Sources/SotoCore/AWSService.swift +++ b/Sources/SotoCore/AWSService.swift @@ -15,13 +15,22 @@ import struct Foundation.URL import NIOCore -/// Protocol for services objects. Contains a client to communicate with AWS and config for defining how to communicate +/// Services object protocol. Contains a client to communicate with AWS and configuration for defining how to communicate. public protocol AWSService: _SotoSendableProtocol { /// Client used to communicate with AWS var client: AWSClient { get } /// Service context details var config: AWSServiceConfig { get } - /// Patch initialization + /// Create new version of service with patch + /// + /// This is required to support ``with(region:middlewares:timeout:byteBufferAllocator:options:)``. + /// Standard implementation is as follows + /// ```swift + /// public init(from: MyService, patch: AWSServiceConfig.Patch) { + /// self.client = from.client + /// self.config = from.config.with(patch: patch) + /// } + /// ``` init(from: Self, patch: AWSServiceConfig.Patch) } diff --git a/Sources/SotoCore/AWSShapes/Base64Data.swift b/Sources/SotoCore/AWSShapes/Base64Data.swift index 288be8cb1..79570331a 100644 --- a/Sources/SotoCore/AWSShapes/Base64Data.swift +++ b/Sources/SotoCore/AWSShapes/Base64Data.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +/// Holds request or response data that is encoded as base64 during transit public struct AWSBase64Data: _SotoSendable, Codable, Equatable { let base64String: String diff --git a/Sources/SotoCore/AWSShapes/Payload.swift b/Sources/SotoCore/AWSShapes/Payload.swift index 055304697..2a810a95f 100644 --- a/Sources/SotoCore/AWSShapes/Payload.swift +++ b/Sources/SotoCore/AWSShapes/Payload.swift @@ -20,7 +20,10 @@ import NIOPosix import NIOCore #endif -/// Holds a request or response payload. A request payload can be in the form of either a ByteBuffer or a stream function that will supply ByteBuffers to the HTTP client. +/// Holds a request or response payload. +/// +/// A request payload can be in the form of either a ByteBuffer or a stream +/// function that will supply ByteBuffers to the HTTP client. /// A response payload only comes in the form of a ByteBuffer public struct AWSPayload { /// Internal enum diff --git a/Sources/SotoCore/AsyncAwaitSupport/AWSClient+EndpointDiscovery+async.swift b/Sources/SotoCore/AsyncAwaitSupport/AWSClient+EndpointDiscovery+async.swift index a9ef2f84f..f498587c9 100644 --- a/Sources/SotoCore/AsyncAwaitSupport/AWSClient+EndpointDiscovery+async.swift +++ b/Sources/SotoCore/AsyncAwaitSupport/AWSClient+EndpointDiscovery+async.swift @@ -24,7 +24,7 @@ import NIOCore @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension AWSClient { - /// Execute an empty request and return a future with the output object generated from the response + /// Execute an empty request /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL @@ -33,8 +33,6 @@ extension AWSClient { /// - endpointDiscovery: Endpoint discovery helper /// - logger: Logger /// - eventLoop: Optional EventLoop to run everything on - /// - returns: - /// Future containing output object that completes when response is received public func execute( operation operationName: String, path: String, @@ -63,7 +61,7 @@ extension AWSClient { ) } - /// Execute a request with an input object and return a future with the output object generated from the response + /// Execute a request with an input object /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL @@ -74,8 +72,6 @@ extension AWSClient { /// - endpointDiscovery: Endpoint discovery helper /// - logger: Logger /// - eventLoop: Optional EventLoop to run everything on - /// - returns: - /// Future containing output object that completes when response is received public func execute( operation operationName: String, path: String, @@ -108,7 +104,7 @@ extension AWSClient { ) } - /// Execute an empty request and return a future with the output object generated from the response + /// Execute an empty request and return the output object generated from the response /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL @@ -118,7 +114,7 @@ extension AWSClient { /// - logger: Logger /// - eventLoop: Optional EventLoop to run everything on /// - returns: - /// Future containing output object that completes when response is received + /// Output object that completes when response is received @discardableResult public func execute( operation operationName: String, path: String, @@ -147,7 +143,7 @@ extension AWSClient { ) } - /// Execute a request with an input object and return a future with the output object generated from the response + /// Execute a request with an input object and return the output object generated from the response /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL @@ -159,7 +155,7 @@ extension AWSClient { /// - logger: Logger /// - eventLoop: Optional EventLoop to run everything on /// - returns: - /// Future containing output object that completes when response is received + /// Output object that completes when response is received public func execute( operation operationName: String, path: String, @@ -192,16 +188,20 @@ extension AWSClient { ) } - /// Execute a request with an input object and return a future with the output object generated from the response + /// Execute a request with an input object and return the output object generated from the response /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) + /// - serviceConfig: AWS service configuration used in request creation and signing /// - input: Input object - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - hostPrefix: Prefix to append to host name + /// - endpointDiscovery: Endpoint discovery helper + /// - logger: Logger + /// - eventLoop: Optional EventLoop to run everything on + /// - stream: Closure to stream payload response into /// - returns: - /// Future containing output object that completes when response is received + /// Output object that completes when response is received public func execute( operation operationName: String, path: String, diff --git a/Sources/SotoCore/AsyncAwaitSupport/AWSClient+async.swift b/Sources/SotoCore/AsyncAwaitSupport/AWSClient+async.swift index da70cd6bc..ecdae670d 100644 --- a/Sources/SotoCore/AsyncAwaitSupport/AWSClient+async.swift +++ b/Sources/SotoCore/AsyncAwaitSupport/AWSClient+async.swift @@ -52,16 +52,16 @@ extension AWSClient { } } - /// execute a request with an input object and return a future with an empty response + /// execute a request with an input object and an empty response /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) + /// - serviceConfig: AWS Service configuration /// - input: Input object - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call - /// - returns: - /// Empty Future that completes when response is received + /// - hostPrefix: String to prefix host name with + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on public func execute( operation operationName: String, path: String, @@ -96,15 +96,14 @@ extension AWSClient { ) } - /// Execute an empty request and return a future with an empty response + /// Execute an empty request and an empty response /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call - /// - returns: - /// Empty Future that completes when response is received + /// - serviceConfig: AWS Service configuration + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on public func execute( operation operationName: String, path: String, @@ -135,15 +134,16 @@ extension AWSClient { ) } - /// Execute an empty request and return a future with the output object generated from the response + /// Execute an empty request and return the output object generated from the response /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - serviceConfig: AWS Service configuration + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on /// - returns: - /// Future containing output object that completes when response is received + /// Output object that completes when response is received public func execute( operation operationName: String, path: String, @@ -174,16 +174,18 @@ extension AWSClient { ) } - /// Execute a request with an input object and return a future with the output object generated from the response + /// Execute a request with an input object and return the output object generated from the response /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) + /// - serviceConfig: AWS Service configuration /// - input: Input object - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - hostPrefix: String to prefix host name with + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on /// - returns: - /// Future containing output object that completes when response is received + /// Output object that completes when response is received public func execute( operation operationName: String, path: String, @@ -218,16 +220,18 @@ extension AWSClient { ) } - /// Execute a request with an input object and return a future with the output object generated from the response + /// Execute a request with an input object and return the output object generated from the response /// - parameters: /// - operationName: Name of the AWS operation /// - path: path to append to endpoint URL /// - httpMethod: HTTP method to use ("GET", "PUT", "PUSH" etc) + /// - serviceConfig: AWS Service configuration /// - input: Input object - /// - config: AWS service configuration used in request creation and signing - /// - context: additional context for call + /// - hostPrefix: String to prefix host name with + /// - logger: Logger to log request details to + /// - eventLoop: EventLoop to run request on /// - returns: - /// Future containing output object that completes when response is received + /// Output object that completes when response is received public func execute( operation operationName: String, path: String, diff --git a/Sources/SotoCore/AsyncAwaitSupport/CredentialProvider+async.swift b/Sources/SotoCore/AsyncAwaitSupport/CredentialProvider+async.swift index 65a23193c..b82f05dd2 100644 --- a/Sources/SotoCore/AsyncAwaitSupport/CredentialProvider+async.swift +++ b/Sources/SotoCore/AsyncAwaitSupport/CredentialProvider+async.swift @@ -23,7 +23,7 @@ import NIOCore #endif import SotoSignerV4 -/// Async Protocol for providing credentials +/// Async Protocol for providing AWS credentials @available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public protocol AsyncCredentialProvider: CredentialProvider { /// Return credential diff --git a/Sources/SotoCore/Credential/CredentialProvider.swift b/Sources/SotoCore/Credential/CredentialProvider.swift index f1053f393..e91360d92 100644 --- a/Sources/SotoCore/Credential/CredentialProvider.swift +++ b/Sources/SotoCore/Credential/CredentialProvider.swift @@ -18,7 +18,7 @@ import NIOConcurrencyHelpers import NIOCore import SotoSignerV4 -/// Protocol providing future holding a credential +/// Provides AWS credentials public protocol CredentialProvider: _SotoSendableProtocol, CustomStringConvertible { /// Return credential /// - Parameters: @@ -39,7 +39,10 @@ extension CredentialProvider { public var description: String { return "\(type(of: self))" } } -/// A helper struct to defer the creation of a `CredentialProvider` until after the AWSClient has been created. +/// Provides factory functions for `CredentialProvider`s. +/// +/// The factory functions are only called once the `AWSClient` has been setup. This means we can supply +/// things like a `Logger`, `EventLoop` and `HTTPClient` to the credential provider when we construct it. public struct CredentialProviderFactory { /// The initialization context for a `ContextProvider` public struct Context { @@ -66,6 +69,10 @@ public struct CredentialProviderFactory { extension CredentialProviderFactory { /// The default CredentialProvider used to access credentials + /// + /// If running on Linux this will look for credentials in the environment, + /// ECS environment variables, EC2 metadata endpoint and finally the AWS config + /// files. On macOS is looks in the environment and then the config file. public static var `default`: CredentialProviderFactory { #if os(Linux) return .selector(.environment, .ecs, .ec2, .configFile()) @@ -74,19 +81,20 @@ extension CredentialProviderFactory { #endif } - /// Use this method to initialize your custom `CredentialProvider` + /// Create a custom `CredentialProvider` public static func custom(_ factory: @escaping (Context) -> CredentialProvider) -> CredentialProviderFactory { Self(cb: factory) } - /// Use this method to enforce the use of a `CredentialProvider` that uses the environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` to create the credentials. + /// Get `CredentialProvider` details from the environment + /// Looks in environment variables `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN`. public static var environment: CredentialProviderFactory { Self { _ -> CredentialProvider in return StaticCredential.fromEnvironment() ?? NullCredentialProvider() } } - /// Use this method to enforce the use of a `CredentialProvider` that uses static credentials. + /// Return static credentials. public static func `static`(accessKeyId: String, secretAccessKey: String, sessionToken: String? = nil) -> CredentialProviderFactory { Self { _ in StaticCredential( @@ -97,7 +105,7 @@ extension CredentialProviderFactory { } } - /// Use this method to enforce the usage of the Credentials supplied via the ECS Metadata endpoint + /// Use credentials supplied via the ECS Metadata endpoint public static var ecs: CredentialProviderFactory { Self { context in if let provider = ECSMetaDataClient(httpClient: context.httpClient) { @@ -109,7 +117,7 @@ extension CredentialProviderFactory { } } - /// Use this method to enforce the usage of the Credentials supplied via the EC2 Instance Metadata endpoint + /// Use credentials supplied via the EC2 Instance Metadata endpoint public static var ec2: CredentialProviderFactory { Self { context in let provider = InstanceMetaDataClient(httpClient: context.httpClient) @@ -117,7 +125,10 @@ extension CredentialProviderFactory { } } - /// Use this method to load credentials from your AWS cli credentials and optional profile configuration files, normally located at `~/.aws/credentials` and `~/.aws/config`. + /// Use credentials loaded from your AWS config + /// + /// Uses AWS cli credentials and optional profile configuration files, normally located at + /// `~/.aws/credentials` and `~/.aws/config`. public static func configFile( credentialsFilePath: String? = nil, configFilePath: String? = nil, @@ -134,14 +145,17 @@ extension CredentialProviderFactory { } } - /// Enforce the use of no credentials. + /// Don't supply any credentials public static var empty: CredentialProviderFactory { Self { _ in StaticCredential(accessKeyId: "", secretAccessKey: "") } } - /// Use the list of credential providers supplied to get credentials. The first one in the list that manages to supply credentials is the one to use + /// Use the list of credential providers supplied to get credentials. + /// + /// When searching for credentials it will go through the list sequentially and the first credential + /// provider that returns valid credentials will be used. public static func selector(_ providers: CredentialProviderFactory...) -> CredentialProviderFactory { Self { context in if providers.count == 1 { diff --git a/Sources/SotoCore/Credential/DeferredCredentialProvider.swift b/Sources/SotoCore/Credential/DeferredCredentialProvider.swift index 06024df4c..84c9eff1d 100644 --- a/Sources/SotoCore/Credential/DeferredCredentialProvider.swift +++ b/Sources/SotoCore/Credential/DeferredCredentialProvider.swift @@ -16,6 +16,8 @@ import Logging import NIOConcurrencyHelpers import NIOCore +/// Wrap and store result from another credential provider. +/// /// Used for wrapping another credential provider whose `getCredential` method doesn't return instantly and /// is only needed to be called once. After the wrapped `CredentialProvider` has generated a credential this is /// returned instead of calling the wrapped `CredentialProvider's` `getCredentials` again. diff --git a/Sources/SotoCore/Credential/ExpiringCredential.swift b/Sources/SotoCore/Credential/ExpiringCredential.swift index a28bd499a..9cf160754 100644 --- a/Sources/SotoCore/Credential/ExpiringCredential.swift +++ b/Sources/SotoCore/Credential/ExpiringCredential.swift @@ -16,6 +16,7 @@ import struct Foundation.Date import struct Foundation.TimeInterval import SotoSignerV4 +/// Credential provider whose credentials expire over tiem. public protocol ExpiringCredential: Credential { /// Will credential expire within a certain time func isExpiring(within: TimeInterval) -> Bool @@ -28,7 +29,7 @@ public extension ExpiringCredential { } } -/// Basic implementation of a struct conforming to ExpiringCredential to be used with the `RotatingCredentialProvider` +/// Basic implementation of a struct conforming to ExpiringCredential. public struct RotatingCredential: ExpiringCredential { public init(accessKeyId: String, secretAccessKey: String, sessionToken: String?, expiration: Date) { self.accessKeyId = accessKeyId diff --git a/Sources/SotoCore/Credential/RotatingCredentialProvider.swift b/Sources/SotoCore/Credential/RotatingCredentialProvider.swift index c02cae35d..f92fb1d73 100644 --- a/Sources/SotoCore/Credential/RotatingCredentialProvider.swift +++ b/Sources/SotoCore/Credential/RotatingCredentialProvider.swift @@ -18,9 +18,11 @@ import NIOConcurrencyHelpers import NIOCore import SotoSignerV4 +/// Wrap a credential provider that returns an `ExpiringCredential` +/// /// Used for wrapping another credential provider whose `getCredential` method returns an `ExpiringCredential`. /// If no credential is available, or the current credentials are going to expire in the near future the wrapped credential provider -/// `getCredential` is called. If current credentials have not expired they are returned otherwise we wait on new +/// `getCredential` is called again. If current credentials have not expired they are returned otherwise we wait on new /// credentials being provided. public final class RotatingCredentialProvider: CredentialProvider { let remainingTokenLifetimeForUse: TimeInterval diff --git a/Sources/SotoCore/Doc/AWSShape.swift b/Sources/SotoCore/Doc/AWSShape.swift index 18cb4737f..91b2d0eb7 100644 --- a/Sources/SotoCore/Doc/AWSShape.swift +++ b/Sources/SotoCore/Doc/AWSShape.swift @@ -17,7 +17,10 @@ import var Foundation.NSNotFound import class Foundation.NSRegularExpression import struct Foundation.UUID -/// Protocol for the input and output objects for all AWS service commands. They need to be Codable so they can be serialized. They also need to provide details on how their container classes are coded when serializing XML. +/// Protocol for the input and output objects for all AWS service commands. +/// +/// They need to be Codable so they can be serialized. They also need to provide details +/// on how their container classes are coded when serializing XML. public protocol AWSShape: _SotoSendableProtocol { /// The array of members serialization helpers static var _encoding: [AWSMemberEncoding] { get } @@ -79,7 +82,7 @@ extension AWSShape { } } -/// AWSShape that can be encoded +/// AWSShape that can be encoded into API input public protocol AWSEncodableShape: AWSShape & Encodable { /// The XML namespace for the object static var _xmlNamespace: String? { get } @@ -217,7 +220,7 @@ public extension AWSEncodableShape { } } -/// AWSShape that can be decoded +/// AWSShape that can be decoded from API output public protocol AWSDecodableShape: AWSShape & Decodable {} /// AWSShape options. diff --git a/Sources/SotoCore/Doc/ServiceProtocol.swift b/Sources/SotoCore/Doc/ServiceProtocol.swift index 14c7c7166..a3004d8bf 100644 --- a/Sources/SotoCore/Doc/ServiceProtocol.swift +++ b/Sources/SotoCore/Doc/ServiceProtocol.swift @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +/// Communication protocol public enum ServiceProtocol { case json(version: String) case restjson diff --git a/Sources/SotoCore/Encoder/CodableProperties/CollectionCoders.swift b/Sources/SotoCore/Encoder/CodableProperties/CollectionCoders.swift index ad1bd0070..a3ea3374a 100644 --- a/Sources/SotoCore/Encoder/CodableProperties/CollectionCoders.swift +++ b/Sources/SotoCore/Encoder/CodableProperties/CollectionCoders.swift @@ -16,12 +16,14 @@ // ArrayCoder -/// Protocol for array encoding properties. The only property required is the array element name `member` +/// Protocol for array encoding properties. +/// The only property required is the array element name `member` public protocol ArrayCoderProperties { static var member: String { get } } -/// Coder for encoding/decoding Arrays. This is extended to support encoding and decoding based on whether `Element` is `Encodable` or `Decodable`. +/// Coder for encoding/decoding Arrays. +/// This is extended to support encoding and decoding based on whether `Element` is `Encodable` or `Decodable`. public struct ArrayCoder: CustomCoder { public typealias CodableValue = [Element] } @@ -55,14 +57,16 @@ extension ArrayCoder: CustomEncoder where Element: Encodable { // DictionaryCoder -/// Protocol for dictionary encoding properties. The property required are the dictionary element name `entry`, the key name `key` and the value name `value` +/// Protocol for dictionary encoding properties. +/// The property required are the dictionary element name `entry`, the key name `key` and the value name `value` public protocol DictionaryCoderProperties { static var entry: String? { get } static var key: String { get } static var value: String { get } } -/// Coder for encoding/decoding Dictionaries. This is extended to support encoding and decoding based on whether `Key` and `Value` are `Encodable` or `Decodable`. +/// Coder for encoding/decoding Dictionaries. +/// This is extended to support encoding and decoding based on whether `Key` and `Value` are `Encodable` or `Decodable`. public struct DictionaryCoder: CustomCoder { public typealias CodableValue = [Key: Value] } diff --git a/Sources/SotoCore/Errors/Error.swift b/Sources/SotoCore/Errors/Error.swift index 1ce5e135a..6788f1e90 100644 --- a/Sources/SotoCore/Errors/Error.swift +++ b/Sources/SotoCore/Errors/Error.swift @@ -18,7 +18,9 @@ import NIOHTTP1 #endif -/// Standard Error type returned by Soto. Initialized with error code and message. Must provide an implementation of var description : String +/// Standard Error type returned by Soto. +/// +/// Initialized with error code and message. public protocol AWSErrorType: Error, CustomStringConvertible { /// initialize error init?(errorCode: String, context: AWSErrorContext) @@ -58,7 +60,7 @@ public struct AWSErrorContext { } } -/// Standard Response Error type returned by Soto. If the error code is unrecognised then this is returned +/// Response Error type returned by Soto if the error code is unrecognised public struct AWSResponseError: AWSErrorType { public let errorCode: String public let context: AWSErrorContext? @@ -73,7 +75,9 @@ public struct AWSResponseError: AWSErrorType { } } -/// Unrecognised error. Used when we cannot extract an error code from the AWS response. Returns full body of error response +/// Raw unprocessed error. +/// +/// Used when we cannot extract an error code from the AWS response. Returns full body of error response public struct AWSRawError: Error, CustomStringConvertible { public let rawBody: String? public let context: AWSErrorContext diff --git a/Sources/SotoCore/Message/AWSMiddleware.swift b/Sources/SotoCore/Message/AWSMiddleware.swift index 75f001d09..e3fcf1442 100644 --- a/Sources/SotoCore/Message/AWSMiddleware.swift +++ b/Sources/SotoCore/Message/AWSMiddleware.swift @@ -24,7 +24,7 @@ public struct AWSMiddlewareContext { public let options: AWSServiceConfig.Options } -/// Middleware protocol. Gives ability to process requests before they are sent to AWS and process responses before they are converted into output shapes +/// Middleware protocol. Process requests before they are sent to AWS and process responses before they are converted into output shapes public protocol AWSServiceMiddleware: _SotoSendableProtocol { /// Process AWSRequest before it is converted to a HTTPClient Request to be sent to AWS func chain(request: AWSRequest, context: AWSMiddlewareContext) throws -> AWSRequest @@ -44,7 +44,7 @@ public extension AWSServiceMiddleware { } } -/// Middleware struct that outputs the contents of requests being sent to AWS and the bodies of the responses received +/// Middleware that outputs the contents of requests being sent to AWS and the contents of the responses received. public struct AWSLoggingMiddleware: AWSServiceMiddleware { #if compiler(>=5.6) public typealias LoggingFunction = @Sendable (String) -> Void diff --git a/Sources/SotoCore/Message/AWSRequest.swift b/Sources/SotoCore/Message/AWSRequest.swift index 0a1cda0a6..979d1683e 100644 --- a/Sources/SotoCore/Message/AWSRequest.swift +++ b/Sources/SotoCore/Message/AWSRequest.swift @@ -24,12 +24,19 @@ import SotoSignerV4 /// Object encapsulating all the information needed to generate a raw HTTP request to AWS public struct AWSRequest { + /// request AWS region public let region: Region + /// request URL public var url: URL + /// request communication protocol public let serviceProtocol: ServiceProtocol + /// AWS operation name public let operation: String + /// request HTTP method public let httpMethod: HTTPMethod + /// request headers public var httpHeaders: HTTPHeaders + /// request body public var body: Body /// Create HTTP Client request from AWSRequest. diff --git a/Sources/SotoSignerV4/signer.swift b/Sources/SotoSignerV4/signer.swift index 38fa2fee1..4eb1f4d7e 100644 --- a/Sources/SotoSignerV4/signer.swift +++ b/Sources/SotoSignerV4/signer.swift @@ -24,9 +24,9 @@ import SotoCrypto /// Amazon Web Services V4 Signer public struct AWSSigner: _SignerSendable { - /// security credentials for accessing AWS services + /// Security credentials for accessing AWS services public let credentials: Credential - /// service signing name. In general this is the same as the service name + /// Service signing name. In general this is the same as the service name public let name: String /// AWS region you are working in public let region: String @@ -42,15 +42,22 @@ public struct AWSSigner: _SignerSendable { self.region = region } - /// Enum for holding your body data + /// Enum for holding request payload public enum BodyData { + /// String case string(String) + /// Data case data(Data) + /// SwiftNIO ByteBuffer case byteBuffer(ByteBuffer) + /// Don't use body when signing request case unsignedPayload + /// Internally used when S3 streamed payloads case s3chunked } + /// Process URL before signing + /// /// `signURL` and `signHeaders` make assumptions about the URLs they are provided, this function cleans up a URL so it is ready /// to be signed by either of these functions. It sorts the query params and ensures they are properly percent encoded public func processURL(url: URL) -> URL? { @@ -73,6 +80,14 @@ public struct AWSSigner: _SignerSendable { } /// Generate signed headers, for a HTTP request + /// - Parameters: + /// - url: Request URL + /// - method: Request HTTP method + /// - headers: Request headers + /// - body: Request body + /// - omitSecurityToken: Should we include security token in the query parameters + /// - date: Date that URL is valid from, defaults to now + /// - Returns: Request headers with added "authorization" header that contains request signature public func signHeaders( url: URL, method: HTTPMethod = .GET, @@ -111,6 +126,15 @@ public struct AWSSigner: _SignerSendable { } /// Generate a signed URL, for a HTTP request + /// - Parameters: + /// - url: Request URL + /// - method: Request HTTP method + /// - headers: Request headers + /// - body: Request body + /// - expires: How long before the signed URL expires + /// - omitSecurityToken: Should we include security token in the query parameters + /// - date: Date that URL is valid from, defaults to now + /// - Returns: Signed URL public func signURL( url: URL, method: HTTPMethod = .GET, @@ -156,14 +180,19 @@ public struct AWSSigner: _SignerSendable { return signedURL } + /// Temporary structure passed from calls to `startSigningChunks` and + /// subsequent calls to `signChunk` public struct ChunkedSigningData { + /// signature for streamed data public let signature: String let datetime: String let signingKey: SymmetricKey } - /// Start the process of signing a s3 chunked upload. Update headers and generate first signature. See https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html - /// for more details + /// Start the process of signing a s3 chunked upload. + /// + /// Update headers and generate first signature. See https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html + /// for more details /// - Parameters: /// - url: url /// - method: http method diff --git a/scripts/build-docc.sh b/scripts/build-docc.sh new file mode 100755 index 000000000..7c9466cb0 --- /dev/null +++ b/scripts/build-docc.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -eux + +TEMP_DIR="$(pwd)/temp" + +cleanup() +{ + if [ -n "$TEMP_DIR" ]; then + rm -rf $TEMP_DIR + fi +} +trap cleanup exit $? + +VERSION=6.x.x +SG_FOLDER=.build/symbol-graphs +SOTOCORE_SG_FOLDER=.build/soto-core-symbol-graphs +OUTPUT_PATH=docs/soto-core/$VERSION + +BUILD_SYMBOLS=1 + +while getopts 's' option +do + case $option in + s) BUILD_SYMBOLS=0;; + esac +done + +if [ -z "${DOCC_HTML_DIR:-}" ]; then + git clone https://github.com/apple/swift-docc-render-artifact $TEMP_DIR/swift-docc-render-artifact + export DOCC_HTML_DIR="$TEMP_DIR/swift-docc-render-artifact/dist" +fi + +if test "$BUILD_SYMBOLS" == 1; then + # build symbol graphs + mkdir -p $SG_FOLDER + swift build \ + -Xswiftc -emit-symbol-graph \ + -Xswiftc -emit-symbol-graph-dir -Xswiftc $SG_FOLDER + # Copy SotoCore symbol graph into separate folder + mkdir -p $SOTOCORE_SG_FOLDER + cp $SG_FOLDER/SotoCore* $SOTOCORE_SG_FOLDER + cp $SG_FOLDER/SotoSignerV4* $SOTOCORE_SG_FOLDER +fi + +# Build documentation +mkdir -p $OUTPUT_PATH +rm -rf $OUTPUT_PATH/* +docc convert SotoCore.docc \ + --transform-for-static-hosting \ + --hosting-base-path /soto-core/$VERSION \ + --fallback-display-name SotoCore \ + --fallback-bundle-identifier codes.soto.soto-core \ + --fallback-bundle-version 1 \ + --additional-symbol-graph-dir $SOTOCORE_SG_FOLDER \ + --output-path $OUTPUT_PATH