diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bb6520f..cdea2f8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -14,13 +14,13 @@ jobs: ruby-version: 3.3 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ env.ruby-version }} - - uses: actions/cache@v3 + - uses: actions/cache@v5 with: path: vendor/bundle key: gems-${{ runner.os }}-${{ env.ruby-version }}-${{ hashFiles('**/Gemfile.lock') }} @@ -37,7 +37,7 @@ jobs: - run: bundle exec middleman build - name: Deploy - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./build diff --git a/.gitignore b/.gitignore index 1d5d08d..157aa09 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,10 @@ doc/ # Vagrant artifacts ubuntu-*-console.log + +# OpenTofu / Terraform +infra/.terraform/ +infra/.terraform.lock.hcl +infra/terraform.tfstate +infra/terraform.tfstate.backup +infra/terraform.tfvars diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 37d02a6..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -3.3.8 diff --git a/Gemfile b/Gemfile index ac90ef4..248ab89 100644 --- a/Gemfile +++ b/Gemfile @@ -8,6 +8,6 @@ gem 'middleman-autoprefixer', '~> 3.0' gem 'middleman-sprockets', '~> 4.1' gem 'rouge', '~> 3.21' gem 'redcarpet', '~> 3.6.0' -gem 'nokogiri', '~> 1.19.1' +gem 'nokogiri', '~> 1.19.3' gem 'sass' gem 'webrick' diff --git a/Gemfile.lock b/Gemfile.lock index cb0f705..80d7a27 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -93,7 +93,7 @@ GEM rouge (~> 3.2) mini_portile2 (2.8.9) minitest (5.27.0) - nokogiri (1.19.1) + nokogiri (1.19.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) padrino-helpers (0.15.2) @@ -146,7 +146,7 @@ DEPENDENCIES middleman-autoprefixer (~> 3.0) middleman-sprockets (~> 4.1) middleman-syntax (~> 3.2) - nokogiri (~> 1.19.1) + nokogiri (~> 1.19.3) redcarpet (~> 3.6.0) rouge (~> 3.21) sass diff --git a/config.rb b/config.rb index dfc9dd4..4a658a1 100644 --- a/config.rb +++ b/config.rb @@ -32,12 +32,6 @@ activate :sprockets -activate :autoprefixer do |config| - config.browsers = ['last 2 version', 'Firefox ESR'] - config.cascade = false - config.inline = true -end - # Github pages require relative links activate :relative_assets set :relative_links, true diff --git a/infra/buildspec.yml b/infra/buildspec.yml new file mode 100644 index 0000000..4299fa3 --- /dev/null +++ b/infra/buildspec.yml @@ -0,0 +1,21 @@ +version: 0.2 + +phases: + install: + runtime-versions: + ruby: 3.3 + commands: + - gem install bundler --no-document + - bundle install --jobs 4 --retry 3 + build: + commands: + - bundle exec middleman build --verbose + post_build: + commands: + - | + if [ "$CODEBUILD_BUILD_SUCCEEDING" = "1" ]; then + aws s3 sync build/ s3://$S3_BUCKET/ --delete + else + echo "Build failed, skipping deploy" + exit 1 + fi diff --git a/infra/main.tf b/infra/main.tf new file mode 100644 index 0000000..7830845 --- /dev/null +++ b/infra/main.tf @@ -0,0 +1,219 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +# S3 bucket for the static site +resource "aws_s3_bucket" "site" { + bucket = var.domain +} + +resource "aws_s3_bucket_website_configuration" "site" { + bucket = aws_s3_bucket.site.id + + index_document { + suffix = "index.html" + } +} + +resource "aws_s3_bucket_public_access_block" "site" { + bucket = aws_s3_bucket.site.id + + block_public_acls = false + block_public_policy = false + ignore_public_acls = false + restrict_public_buckets = false +} + +resource "aws_s3_bucket_policy" "site" { + bucket = aws_s3_bucket.site.id + depends_on = [aws_s3_bucket_public_access_block.site] + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Principal = "*" + Action = "s3:GetObject" + Resource = "${aws_s3_bucket.site.arn}/*" + }] + }) +} + +# Artifact bucket for CodePipeline (private) +resource "aws_s3_bucket" "artifacts" { + bucket = "${var.domain}-pipeline-artifacts" +} + +resource "aws_s3_bucket_lifecycle_configuration" "artifacts" { + bucket = aws_s3_bucket.artifacts.id + + rule { + id = "expire" + status = "Enabled" + filter {} + expiration { + days = 7 + } + } +} + +# CodeBuild +resource "aws_iam_role" "codebuild" { + name = "api-docs-codebuild" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Principal = { Service = "codebuild.amazonaws.com" } + Action = "sts:AssumeRole" + }] + }) +} + +resource "aws_iam_role_policy" "codebuild" { + role = aws_iam_role.codebuild.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"] + Resource = "*" + }, + { + Effect = "Allow" + Action = ["s3:PutObject", "s3:DeleteObject", "s3:ListBucket"] + Resource = [aws_s3_bucket.site.arn, "${aws_s3_bucket.site.arn}/*"] + }, + { + Effect = "Allow" + Action = ["s3:GetObject", "s3:GetObjectVersion", "s3:PutObject"] + Resource = "${aws_s3_bucket.artifacts.arn}/*" + }, + { + Effect = "Allow" + Action = "codestar-connections:UseConnection" + Resource = var.github_connection_arn + } + ] + }) +} + +resource "aws_codebuild_project" "site" { + name = "api-docs" + service_role = aws_iam_role.codebuild.arn + + artifacts { + type = "CODEPIPELINE" + } + + environment { + compute_type = "BUILD_GENERAL1_SMALL" + image = "aws/codebuild/standard:7.0" + type = "LINUX_CONTAINER" + + environment_variable { + name = "S3_BUCKET" + value = aws_s3_bucket.site.bucket + } + } + + source { + type = "CODEPIPELINE" + buildspec = "infra/buildspec.yml" + } +} + +# CodePipeline +resource "aws_iam_role" "codepipeline" { + name = "api-docs-codepipeline" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [{ + Effect = "Allow" + Principal = { Service = "codepipeline.amazonaws.com" } + Action = "sts:AssumeRole" + }] + }) +} + +resource "aws_iam_role_policy" "codepipeline" { + role = aws_iam_role.codepipeline.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = ["s3:GetObject", "s3:GetObjectVersion", "s3:PutObject", "s3:ListBucket"] + Resource = [aws_s3_bucket.artifacts.arn, "${aws_s3_bucket.artifacts.arn}/*"] + }, + { + Effect = "Allow" + Action = ["codebuild:BatchGetBuilds", "codebuild:StartBuild"] + Resource = aws_codebuild_project.site.arn + }, + { + Effect = "Allow" + Action = "codestar-connections:UseConnection" + Resource = var.github_connection_arn + } + ] + }) +} + +resource "aws_codepipeline" "site" { + name = "api-docs" + role_arn = aws_iam_role.codepipeline.arn + + artifact_store { + location = aws_s3_bucket.artifacts.bucket + type = "S3" + } + + stage { + name = "Source" + action { + name = "Source" + category = "Source" + owner = "AWS" + provider = "CodeStarSourceConnection" + version = "1" + output_artifacts = ["source"] + configuration = { + ConnectionArn = var.github_connection_arn + FullRepositoryId = "jetbuilt/jetbuilt-api-docs" + BranchName = "main" + OutputArtifactFormat = "CODEBUILD_CLONE_REF" + DetectChanges = "true" + } + } + } + + stage { + name = "Build" + action { + name = "Build" + category = "Build" + owner = "AWS" + provider = "CodeBuild" + version = "1" + input_artifacts = ["source"] + configuration = { + ProjectName = aws_codebuild_project.site.name + } + } + } +} diff --git a/infra/outputs.tf b/infra/outputs.tf new file mode 100644 index 0000000..3da9a95 --- /dev/null +++ b/infra/outputs.tf @@ -0,0 +1,4 @@ +output "site_endpoint" { + description = "S3 website endpoint — use this as the Cloudflare origin" + value = aws_s3_bucket_website_configuration.site.website_endpoint +} diff --git a/infra/variables.tf b/infra/variables.tf new file mode 100644 index 0000000..41435d5 --- /dev/null +++ b/infra/variables.tf @@ -0,0 +1,7 @@ +variable "domain" { + default = "api.jetbuilt.com" +} + +variable "github_connection_arn" { + description = "ARN of the existing CodeStar connection for GitHub" +} diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..43c0e28 --- /dev/null +++ b/mise.toml @@ -0,0 +1,3 @@ +[tools] +node = "24" +ruby = "3.3"