diff --git a/elixir/postgrex/.formatter.exs b/elixir/postgrex/.formatter.exs new file mode 100644 index 00000000..682fc6c6 --- /dev/null +++ b/elixir/postgrex/.formatter.exs @@ -0,0 +1,3 @@ +[ + inputs: ["{mix,.formatter}.exs", "{lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/postgrex/.gitignore b/elixir/postgrex/.gitignore new file mode 100644 index 00000000..63ec917a --- /dev/null +++ b/elixir/postgrex/.gitignore @@ -0,0 +1,9 @@ +/_build/ +/deps/ +/doc/ +/.fetch +erl_crash.dump +*.ez +*.beam +/config/*.secret.exs +.elixir_ls/ diff --git a/elixir/postgrex/README.md b/elixir/postgrex/README.md new file mode 100644 index 00000000..87992ba1 --- /dev/null +++ b/elixir/postgrex/README.md @@ -0,0 +1,87 @@ +# Aurora DSQL with Postgrex + +## Overview + +This code example demonstrates how to use Postgrex with Amazon Aurora DSQL. The example shows you how to connect to an Aurora DSQL cluster and perform basic database operations. + +Aurora DSQL is a distributed SQL database service that provides high availability and scalability for your PostgreSQL-compatible applications. Postgrex is the PostgreSQL driver for Elixir that allows you to interact with PostgreSQL databases. + +## About the code example + +The example demonstrates a flexible connection approach that works for both admin and non-admin users: + +* When connecting as an **admin user**, the example uses the `public` schema and generates an admin authentication token. +* When connecting as a **non-admin user**, the example uses a custom `myschema` schema and generates a standard authentication token. + +The code automatically detects the user type and adjusts its behavior accordingly. + +## ⚠️ Important + +* Running this code might result in charges to your AWS account. +* We recommend that you grant your code least privilege. At most, grant only the minimum permissions required to perform the task. For more information, see [Grant least privilege](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege). +* This code is not tested in every AWS Region. For more information, see [AWS Regional Services](https://aws.amazon.com/about-aws/global-infrastructure/regional-product-services). + +## Run the example + +### Prerequisites + +* You must have an AWS account, and have your default credentials and AWS Region configured as described in the [Globally configuring AWS SDKs and tools](https://docs.aws.amazon.com/credref/latest/refdocs/creds-config-files.html) guide. +* [Elixir 1.14+](https://elixir-lang.org/install.html) installed. + +```bash +elixir --version +``` +* Install required Elixir dependencies. + +mix deps.get +* AWS CLI installed and configured for token generation. +* You must have an Aurora DSQL cluster. For information about creating an Aurora DSQL cluster, see the [Getting started with Aurora DSQL](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/getting-started.html) guide. +* If connecting as a non-admin user, ensure the user is linked to an IAM role and is granted access to the `myschema` schema. See the [Using database roles with IAM roles](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/using-database-and-iam-roles.html) guide. + +### Run the code + +The example demonstrates the following operations: + +- Opening a connection to an Aurora DSQL cluster +- Creating a table +- Inserting and querying data + +The example is designed to work with both admin and non-admin users: + +- When run as an admin user, it uses the `public` schema +- When run as a non-admin user, it uses the `myschema` schema + +**Note:** running the example will use actual resources in your AWS account and may incur charges. + +Set environment variables for your cluster details: + +```bash +# e.g. "admin" +export CLUSTER_USER="" + +# e.g. "foo0bar1baz2quux3quuux4.dsql.us-east-1.on.aws" +export CLUSTER_ENDPOINT="" + +# e.g. "us-east-1" +export REGION="" +``` + +Install dependencies and run the example: + +```bash +mix run -e "AuroraDsqlExample.run()" +``` + +The example contains comments explaining the code and the operations being performed. + +## Additional resources + +* [Amazon Aurora DSQL Documentation](https://docs.aws.amazon.com/aurora-dsql/latest/userguide/what-is-aurora-dsql.html) +* [Postgrex Documentation](https://hexdocs.pm/postgrex/) +* [Elixir Documentation](https://elixir-lang.org/docs.html) + +--- + +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/elixir/postgrex/lib/example.ex b/elixir/postgrex/lib/example.ex new file mode 100644 index 00000000..e29513d3 --- /dev/null +++ b/elixir/postgrex/lib/example.ex @@ -0,0 +1,84 @@ +defmodule AuroraDsqlExample do + @moduledoc """ + Example demonstrating Aurora DSQL connectivity with Elixir using Postgrex. + """ + + def run do + cluster_endpoint = System.get_env("CLUSTER_ENDPOINT") || raise "CLUSTER_ENDPOINT not set" + cluster_user = System.get_env("CLUSTER_USER") || raise "CLUSTER_USER not set" + region = System.get_env("REGION") || raise "REGION not set" + + token = generate_auth_token(cluster_endpoint, cluster_user, region) + schema = if cluster_user == "admin", do: "public", else: "myschema" + + opts = [ + hostname: cluster_endpoint, + port: 5432, + username: cluster_user, + password: token, + database: "postgres", + ssl: true, + ssl_opts: [ + verify: :verify_peer, + cacerts: :public_key.cacerts_get(), + customize_hostname_check: [ + match_fun: :public_key.pkix_verify_hostname_match_fun(:https) + ] + ] + ] + + {:ok, conn} = Postgrex.start_link(opts) + + try do + Postgrex.query!(conn, "SET search_path = #{schema}", []) + exercise_connection(conn) + IO.puts("Connection exercised successfully") + after + GenServer.stop(conn) + end + end + + defp generate_auth_token(endpoint, user, region) do + action = if user == "admin", do: "generate-db-connect-admin-auth-token", else: "generate-db-connect-auth-token" + + {output, 0} = System.cmd("aws", [ + "dsql", + action, + "--hostname", endpoint, + "--region", region + ]) + + String.trim(output) + end + + defp exercise_connection(conn) do + Postgrex.query!(conn, """ + CREATE TABLE IF NOT EXISTS owner ( + id UUID NOT NULL DEFAULT gen_random_uuid(), + name VARCHAR(30) NOT NULL, + city VARCHAR(80) NOT NULL, + telephone VARCHAR(20) DEFAULT NULL, + PRIMARY KEY (id) + ) + """, []) + + Postgrex.query!(conn, + "INSERT INTO owner (name, city, telephone) VALUES ($1, $2, $3)", + ["John Doe", "Anytown", "555-555-1999"] + ) + + result = Postgrex.query!(conn, + "SELECT * FROM owner WHERE name = $1", + ["John Doe"] + ) + + [row] = result.rows + [_id, name, city, telephone] = row + + unless name == "John Doe" and city == "Anytown" and telephone == "555-555-1999" do + raise "Unexpected data retrieved" + end + + Postgrex.query!(conn, "DELETE FROM owner WHERE name = $1", ["John Doe"]) + end +end diff --git a/elixir/postgrex/mix.exs b/elixir/postgrex/mix.exs new file mode 100644 index 00000000..94a905b6 --- /dev/null +++ b/elixir/postgrex/mix.exs @@ -0,0 +1,25 @@ +defmodule AuroraDsqlExample.MixProject do + use Mix.Project + + def project do + [ + app: :aurora_dsql_example, + version: "0.1.0", + elixir: "~> 1.14", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + def application do + [ + extra_applications: [:logger, :crypto, :public_key] + ] + end + + defp deps do + [ + {:postgrex, "~> 0.19"} + ] + end +end diff --git a/elixir/postgrex/mix.lock b/elixir/postgrex/mix.lock new file mode 100644 index 00000000..f7ca9809 --- /dev/null +++ b/elixir/postgrex/mix.lock @@ -0,0 +1,6 @@ +%{ + "db_connection": {:hex, :db_connection, "2.8.1", "9abdc1e68c34c6163f6fb96a96532272d13ad7ca45262156ae8b7ec6d9dc4bec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a61a3d489b239d76f326e03b98794fb8e45168396c925ef25feb405ed09da8fd"}, + "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"}, + "postgrex": {:hex, :postgrex, "0.21.1", "2c5cc830ec11e7a0067dd4d623c049b3ef807e9507a424985b8dcf921224cd88", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "27d8d21c103c3cc68851b533ff99eef353e6a0ff98dc444ea751de43eb48bdac"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, +}