diff --git a/.gitea/workflows/infrastructure-deploy.yml b/.gitea/workflows/infrastructure-deploy.yml index 6b4a228..44f8dc3 100644 --- a/.gitea/workflows/infrastructure-deploy.yml +++ b/.gitea/workflows/infrastructure-deploy.yml @@ -23,22 +23,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - # Broad infra-provisioning credentials, distinct from the narrowly - # scoped gitea-ci-user used by the app deploy workflows (ECR push + - # ecs:UpdateService only). Pulumi needs to create/update IAM, RDS, - # ECS, CloudFront, etc., so this identity is intentionally wider. - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - aws-access-key-id: ${{ secrets.PULUMI_AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.PULUMI_AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: infrastructure/go.mod - # Stack name assumes the runner is logged into the same Pulumi Cloud # org as `pulumi.yaml`'s default; if PULUMI_ACCESS_TOKEN's org differs, # qualify this as "/main" instead. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..463ebfd --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/volume diff --git a/cms/main.go b/cms/main.go index 68841a1..0a5809c 100644 --- a/cms/main.go +++ b/cms/main.go @@ -66,8 +66,20 @@ func runServer() { panic(fmt.Errorf("missing required env var: MEDIACONVERT_OUTPUT_BUCKET")) } + // AWS_ENDPOINT_URL is only ever set when pointing at something other than + // real S3 — LocalStack, in docker-compose. Virtual-host addressing would + // resolve . there, which neither Docker's DNS nor + // LocalStack's own wildcard domain serves from inside a container, so those + // deployments need path-style URLs. Left off against real S3, which has + // been steering away from path-style for new buckets. + s3Options := func(o *s3.Options) { + if os.Getenv("AWS_ENDPOINT_URL") != "" || os.Getenv("AWS_ENDPOINT_URL_S3") != "" { + o.UsePathStyle = true + } + } + concreteS3Client := &services.S3Concrete{ - S3Client: s3.NewFromConfig(awsConfig), + S3Client: s3.NewFromConfig(awsConfig, s3Options), Bucket: bucketName, } concreteS3Client.AssertSuccessfulConnection(context.Background()) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1b2a869 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,107 @@ +services: + localstack: + container_name: "${LOCALSTACK_DOCKER_NAME:-localstack-main}" + image: localstack/localstack + ports: + - "127.0.0.1:4566:4566" # LocalStack Gateway + - "127.0.0.1:4510-4559:4510-4559" # external services port range + - "127.0.0.1:443:443" # LocalStack HTTPS Gateway + environment: + # Activate LocalStack for AWS: https://docs.localstack.cloud/getting-started/auth-token/ + - LOCALSTACK_AUTH_TOKEN=${LOCALSTACK_AUTH_TOKEN:?} # required for Pro + # LocalStack configuration: https://docs.localstack.cloud/references/configuration/ + - DEBUG=${DEBUG:-0} + - PERSISTENCE=${PERSISTENCE:-0} + volumes: + - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack" + - "/var/run/docker.sock:/var/run/docker.sock" + networks: + default: + aliases: + # Publicly this name resolves to 127.0.0.1, so the endpoint in + # infrastructure/Pulumi.local.yaml works from the host; the alias + # makes the same name resolve here from inside the network. + - localhost.localstack.cloud + + # Provisions the buckets, the Postgres instance and the IAM roles inside + # LocalStack, by running infrastructure/ — the same program that deploys + # production, on its "local" stack. Runs to completion before cms starts. + # Equivalent to `pulumi stack select local && pulumi up` from the host. + infra: + image: pulumi/pulumi-go:latest + working_dir: /infra + entrypoint: ["/bin/sh", "-c"] + # One list element on purpose: a string command is split on whitespace, and + # `sh -c` would then take just the first word as its script. + command: + - > + pulumi login file:///state && + pulumi stack select local --create && + pulumi up --yes --non-interactive + environment: + - PULUMI_CONFIG_PASSPHRASE=${PULUMI_CONFIG_PASSPHRASE:-local} + - PULUMI_SKIP_UPDATE_CHECK=true + - DB_PASSWORD=${DB_PASSWORD:?} + volumes: + - "./infrastructure:/infra" + - "pulumi-state:/state" # stack state, kept out of the repo + - "pulumi-home:/root/.pulumi" # provider plugins, downloaded once + - "go-mod-cache:/go/pkg/mod" + depends_on: + localstack: + condition: service_healthy + develop: + watch: + # The program is bind-mounted, not baked into the image, so there is + # nothing to rebuild — the restart is what re-runs `pulumi up`. + - action: sync+restart + path: ./infrastructure + target: /infra + ignore: + - README.md + + cms: + container_name: "${CMS_DOCKER_NAME:-cms}" + build: + context: ./cms + dockerfile: Dockerfile + ports: + - "127.0.0.1:8081:8081" # JSON API + Swagger UI at /swagger/ + environment: + # AWS: point the SDK at LocalStack instead of the real endpoints. This + # host is deliberately not "localstack": it is baked into the presigned + # upload URLs this service hands out, so it has to resolve for whoever + # PUTs the file — on the host as well as inside this network. + - AWS_REGION=${AWS_REGION:-us-east-1} + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-test} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY:-test} + - AWS_ENDPOINT_URL=${AWS_ENDPOINT_URL:-http://localhost.localstack.cloud:4566} + # Bucket and role wiring — see main.go, which panics if any is empty. The + # bucket names are pinned by the localstack branch in infrastructure/main.go. + - S3_BUCKET=${S3_BUCKET:-raw-uploads-bucket} + - MEDIACONVERT_INPUT_BUCKET=${MEDIACONVERT_INPUT_BUCKET:-raw-uploads-bucket} + - MEDIACONVERT_OUTPUT_BUCKET=${MEDIACONVERT_OUTPUT_BUCKET:-encoded-bucket} + - MEDIACONVERT_ROLE_ARN=${MEDIACONVERT_ROLE_ARN:-arn:aws:iam::000000000000:role/mediaconvert-service-role} + # Postgres: the RDS instance LocalStack provisions, which runs inside the + # localstack container and speaks plain TCP — hence sslmode=disable. + - DB_HOST=${DB_HOST:-localhost.localstack.cloud} + - DB_PORT=${DB_PORT:-4510} + - DB_NAME=${DB_NAME:-cms} + - DB_USER=${DB_USER:-cms} + - DB_PASSWORD=${DB_PASSWORD:?} + - DB_SSLMODE=${DB_SSLMODE:-disable} + depends_on: + infra: + condition: service_completed_successfully + develop: + watch: + # cms is a compiled binary, so there is nothing useful to sync into the + # running container — every change means a new image. Needs + # `docker compose up --watch` (or `docker compose watch`) to take effect. + - action: rebuild + path: ./cms + +volumes: + pulumi-state: + pulumi-home: + go-mod-cache: diff --git a/infrastructure/Pulumi.local.yaml b/infrastructure/Pulumi.local.yaml index f42697a..6a98079 100644 --- a/infrastructure/Pulumi.local.yaml +++ b/infrastructure/Pulumi.local.yaml @@ -23,3 +23,4 @@ config: s3: http://localhost.localstack.cloud:4566 sts: http://localhost.localstack.cloud:4566 thamanyah:localstack: "true" +encryptionsalt: v1:QKoqcSNBvDc=:v1:bTGZko5jHuGNRVoq:2oQ4pFb8MYwspEbxipYeO+rVqVnLBg== diff --git a/infrastructure/thamanyah b/infrastructure/thamanyah deleted file mode 100755 index b4914f1..0000000 Binary files a/infrastructure/thamanyah and /dev/null differ