DEV Community

Cover image for Developer Self-Service with Resourcely
Derek Morgan
Derek Morgan

Posted on • Edited on

Developer Self-Service with Resourcely

Intro

The Scenario

Thanks to the success of Infrastructure-as-Code tools, more organizations are allowing their developers to deploy infrastructure resources themselves. Examples of these resources could be AWS S3 buckets for storage, containers for their applications, serverless functions, and anything else needed. While this paradigm shift can reduce the overall load on the infrastructure developers, you must make considerations when shifting that load.

  1. Productivity waste due to context switching.
  2. Security risks due to expanded access to infrastructure.
  3. Reliability and stability risks due to misconfigurations.

Developing a robust self-service infrastructure platform using Resourcely's Blueprints and Guardrails can address all of these concerns with proper planning.

Let's examine a simple AWS S3 deployment to see how Resourcely's features can empower your developers to deploy their infrastructure securely.

Prerequisites

To integrate Resourcely into your deployment pipeline, you'll need to configure a few things. In this example, I'll use a GitHub Actions Pipeline.

GitHub Repository

You'll need to configure a GitHub repository and provide access to Resourcely. If you haven't already done this, you can find more information here: https://docs.resourcely.io/integrate/source-code-management/github.

Terraform code

Start with some basic Terraform code. We'll add more to this, but this is to verify the workflow in the subsequent steps works:

main.tf

data "aws_region" "current" {}

output "current_region" {
  value = data.aws_region.current.name
}
Enter fullscreen mode Exit fullscreen mode

Terraform backend

You'll need a backend configured for Terraform. You can use any backend you wish, such as one using S3 and DynamoDB:

versions.tf

terraform {
  required_version = "~> 1.9.5"
  backend "s3" {
    bucket         = "resourcely-tf-backend"
    key            = "terraform.tfstate"
    region         = "us-east-1"
    dynamodb_table = "ResourcelyTerraformLocks"
  }
}
Enter fullscreen mode Exit fullscreen mode

GitHub Actions Workflow

You'll then need to configure a GitHub Actions Workflow that authenticates to AWS (I prefer OIDC), deploy Terraform, and allow Resourcely to analyze the state. You can find more information on configuring your workflow and the necessary role here: https://docs.resourcely.io/integrate/terraform-integration/github-actions/local-plan/aws-with-openid-connect

An example of the workflow is below. Ensure you set the role-to-assume secret:

.github/workflows/main.yml

name: Plan and Apply Terraform

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

permissions:
  id-token: write
  contents: read

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    environment: production

    defaults:
      run:
        shell: bash

    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
            role-to-assume: ${{ secrets.ROLE_TO_ASSUME }}
            aws-region: ${{ vars.AWS_REGION }}

      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Terraform Init
        run: terraform init

      - name: Terraform Plan
        run: terraform plan -out=plan.raw

      - name: Convert the plan to JSON
        id: planToJson
        run: terraform show -json plan.raw

      - name: Save JSON to a file
        using: fishcharlie/CmdToFile@v1.0.0
        with:
          data: ${{ steps.planToJson.outputs.stdout }}
          output: plan.json

      - name: Upload Terraform Plan Output
        uses: actions/upload-artifact@v4
        with:
          name: plan-file
          path: plan.json

      - name: Terraform Apply
        if: GitHub.ref == 'refs/heads/main' && github.event_name == 'push'
        run: terraform apply -auto-approve -input=false

  resourcely-ci:
    Needs: terraform
    if: GitHub.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Download Terraform Plan Output
        uses: actions/download-artifact@v4
        with:
          name: plan-file
          path: tf-plan-files/

      - name: Resourcely CI
        uses: Resourcely-Inc/resourcely-action@v1
        with:
          resourcely_api_token: ${{ secrets.RESOURCELY_API_TOKEN }}
          resourcely_api_host: "https://api.resourcely.io"
          tf_plan_directory: "tf-plan-files"

Enter fullscreen mode Exit fullscreen mode

Once everything is in place and you've tested the workflow, let's start building with Resourcely!

Blueprints

What are blueprints?

Resourcely's Blueprints provide a practical way to simplify and standardize cloud resource deployment using customizable templates that generate Terraform configurations. With Blueprints, teams can create consistent, secure, and compliant infrastructure setups, making the deployment process more efficient and improving collaboration across projects.

Configure blueprints

In this tutorial, we'll import an existing module to start our Blueprint. We'll use Resourcely's "Foundry" to author the Blueprint.

Resourcely Foundry

Choose the S3 Bucket option. That should add code similar to the one below to your console:

---
constants:
  __name: "{{ bucket }}_{{ __guid }}"
---

resource "aws_s3_bucket" "{{ __name }}" {
  bucket = "{{ bucket }}"
}

resource "aws_s3_bucket_public_access_block" "{{ __name }}" {
    bucket = aws_s3_bucket.{{ __name }}.id

    block_public_acls       = true
    block_public_policy     = true
    ignore_public_acls      = true
    restrict_public_buckets = true
}

resource "aws_s3_bucket_ownership_controls" "{{ __name }}" {
  bucket = aws_s3_bucket.{{ __name }}.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

resource "aws_s3_bucket_versioning" "{{ __name }}" {
    bucket = aws_s3_bucket.{{ __name }}.id
    versioning_configuration {
        status = "{{ versioning_configuration_status }}"
    }
}
Enter fullscreen mode Exit fullscreen mode

The templating syntax helps ensure each deployment's bucket names and resource IDs are unique. The "constants" section is similar to a "locals" block in HCL. In this case, it generates a unique name by appending the user-defined bucket to the system-defined "GUID."

Developer Experience

When a user deploys a Blueprint, they're not required to modify any code directly. Instead, Resourcely presents users with a clean UI that allows them to define the template's variables easily. To define the bucket in this example, click the "Developer Experience" tab. Resourcely presents you with a text input field for the Bucket and a dropdown for the "Versioning configuration status":

Resourcely Blueprints

Once you define these items, you can preview the Terraform code that will be created by clicking on the "Terraform" tab:

Image description

The GUID of the deployment is appended to all resource IDs, enabling the resources to be deployed multiple times without creating overlapping bucket names. For more information about authoring Blueprints and available variables, see here.

Modifying the Blueprint

Blueprints are straightforward to modify manually, but some very cool features simplify the process of maximizing the developer-friendliness of your blueprints. One of these quality-of-life features is the ability to generate "tags" dynamically based on value type. If you highlight an attribute of one of the resources, you can click the "Use Selection" dropdown followed by the "Generate tag" option to generate a tag. You can also simply right-click the attribute to do the same. In the following image, I select the block_public_acls attribute and generate a tag for it:

Generating Tags

Once you've done this, Resourcely creates a new variable. Feel free to edit this as you wish:

Resourcely Tags

The "Developer Experience" tab now has the new boolean added:

Resourcely DevEx

While this is a limited example, the ability to create a simple developer experience from your even complicated Terraform code could not be more effortless. Typically, you won't parameterize every single attribute in your code as that would not make for a great experience, so we'll add some guardrails to ensure no one makes changes that could cause your deployment to become non-compliant.

Guardrails

What are guardrails?

Resourcely's Guardrails are infrastructure policies seamlessly integrated into developer workflows. They allow you to define rules enforced within your existing CI pipeline, ensuring compliance during deployment. Guardrails can be customized to consider specific contexts and route any Terraform configurations that violate these rules for approval. By integrating Guardrails with Blueprints, developers receive immediate feedback and guidance during configuration, promoting secure and efficient deployments.

The "Really" Policy Language

You create guardrails using the innovative "Really" policy language. If you've used other policy languages, such as Rego, you'll find that the Really policy language is a fantastic alternative. I won't dive too deep into the differences since Travis McPeak already has this fantastic post: Announcing Really. The Really policy language is one of the first things that drew me to Resourcely. I've written countless lines of Rego in my Open Policy Agent policies, and I would have loved to cut those lines down significantly with Really's more concise syntax. Check out the Really docs here.

Add guardrails

To author a guardrail, it's easiest to begin in the Foundry. Click on the "Author a Guardrail" tab, "Select a Guardrail starter" from the dropdown menu, and choose "[S3] Bucket Versioning Enabled." Once you've done that, you will see the generated Really policy-as-code language:

Really Policy-as-Code

You can choose an approver to authorize overrides for the Guardrail. If you haven't created any, "default" will work just fine. Once you set this, click "Create Guardrail" at the top left of the screen, decide if you want this Guardrail to be "Active," "Inactive," or "Evaluate Only," then click "Yes, create Guardrail," choose "Use Guardrail in Blueprint" on the following screen, and this Guardrail is ready to go!

Resourcely Guardrail

Create A Custom Guardrail

Creating a Guardrail from a starter is a great way to get things kicked off, but there certainly won't be a starter Guardrail for every attribute you need to enforce. Let's create a custom Guardrail that will enforce the "object_ownership" attribute within the "aws_s3_ownership_controls" resource to be enforced to "BucketOwnerEnforced":

resource "aws_s3_bucket_ownership_controls" "{{ __name }}" {
  bucket = aws_s3_bucket.{{ __name }}.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}
Enter fullscreen mode Exit fullscreen mode

First, select the "object_ownership" attribute within the resource, click on the "Use Selection" dropdown, and select "Generate Guardrail."

Generate Guardrail

Voila! That simple action generated everything needed to enforce that "object_ownership" setting. Feel free to change it if you so desire, but otherwise, click on "Define Metadata" and complete the fields:

Define Metadata

Once that's finished, click the "Create Guardrail" button again and choose "Use Guardrail in Blueprint" to add it to the list of usable Guardrails.

Managing Guardrail Attachments

Once you create the Guardrails and set them to "active," they're automatically attached to the current Blueprint. If you need to add or remove a Guardrail from a Blueprint, you must edit the Blueprint in Foundry. To disable a Guardrail, simply toggle the slider next to the Guardrail, and it will disappear from the Guardrail list:

Disable Guardrail

Final Touches

Now we've created the Blueprint, generated custom tags, and created Guardrails, let's finalize the process by defining metadata:

Define Metadata
Finally click, "Create Blueprint" at the top right and publish the Blueprint. If you're satisfied with everything, choose "Use Blueprint in a Pull Request," and we'll deploy!

Deployment

Admin Deployment

As long as you configure your repository information correctly, you should be able to pass in the information requested without any issues:

Deployment

You'll notice that the "AWS s3 bucket public access block name block public acls" (I know, I could have been more succinct there) option is "true" and can be toggled to "false." You also may notice that "Versioning configuration status" has no option to change. The Guardrails are live and already benefiting your users. Instead of discovering after you open the PR, you immediately know what your Guardrails allow you to do.

Go ahead and click "Continue." You may see existing code if you've pushed code before using Resourcely. Scroll down, and you'll see any additional code in green:

Deployment

If everything looks good, go ahead and use the button at the top right to Open the pull request, fill in the information, and submit! A status page will greet you.

If the Resourcely action is successful in GitHub, the PR should be Approved!

Approved PR

Approved PR

Dev Deployment

Before we close this guide, let's examine the developer experience by deploying our new S3 Blueprint through the eyes of "Jane Dev."

Jane will see a resources page similar to what you just saw, but much more limited. The developer only has the tools they need to deploy.

Developer View

She'll click on the same "Create Pull Request" button at the top right:

Create PR

Follow the steps, and you'll see the same process as before. Open the pull request on the final step, and the PR should be opened, GitHub will run the pipeline, Resourcely will then approve the PR if everything goes well:

Image description

Merge the PR, and Jane Dev's bucket is deployed with all settings enforced:

Image description

Conclusion

This tutorial covered several ways Resourcely makes life easy for Infrastructure-as-Code developers who need resources they can deploy effortlessly. Simple niceties such as tag generation make it quick and easy to pass in attribute values that weren't previously a variable. Guardrails ensure that you can enforce any attribute. With Blueprints tying everything together, I don't know of another product that makes it easier to deploy compliant resources quickly. Be sure to check out Resourcely with an insanely generous free tier: https://portal.resourcely.io

I hope you enjoyed this tutorial as much as I enjoyed writing it! Until next time, cheers!

Top comments (0)