> For the complete documentation index, see [llms.txt](https://docs.mechcloud.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.mechcloud.io/cloud-computing/stateless-iac/aws/authoring-desired-state.md).

# Authoring desired state

This guide explains how to **author the desired state** in MechCloud Stateless IaC for AWS. You define your infrastructure using a clean, hierarchical YAML format that is inspired by CloudFormation but significantly simpler — no logical IDs, no `!Ref`, no state files, and no `PascalCase`. Instead, you use **nesting for relationships**, **`snake_case` for properties**, and **`ref:` syntax** for cross-resource references.

### Key Principles

| Principle                    | Description                                                                                                           |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| **Hierarchy = Relationship** | Nest resources to define parent-child relationships (e.g., subnet inside VPC).                                        |
| **Unique Relative Paths**    | Every resource must have a unique path (e.g., `vpc1/subnet1/instance1`). You are responsible for ensuring uniqueness. |
| **`snake_case` Properties**  | All keys use lowercase with underscores (e.g., `cidr_block`, not `CidrBlock`).                                        |
| **No Parent IDs**            | Do **not** include `vpc_id`, `subnet_id`, etc. — MechCloud infers them from nesting.                                  |
| **Sibling References**       | Use `ref:<name>` **only** if both resources are in the **same `resources:` block**.                                   |
| **Non-Sibling References**   | Use `ref:<full_path>` to reference resources outside the current parent.                                              |

### Step-by-Step: From CloudFormation to MechCloud

Let’s convert a simple `CloudFormation` stack into a MechCloud desired state.

#### CloudFormation (Original)

```yaml
Resources:
  VPC1:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: "10.2.0.0/16"

  SGWeb:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC1
      GroupDescription: "Allow HTTP from internet"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: "0.0.0.0/0"

  SGService:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC1
      GroupDescription: "Allow app traffic only from web1 SG"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 8080
          ToPort: 8080
          SourceSecurityGroupId: !Ref SGWeb

  Subnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC1
      CidrBlock: "10.2.1.0/24"

  Service1:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: "ami-0bc691261a82b32bc"
      InstanceType: "t3.small"
      SubnetId: !Ref Subnet1
      SecurityGroupIds:
        - !Ref SGService

  Web1:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: "ami-0bc691261a82b32bc"
      InstanceType: "t3.small"
      SubnetId: !Ref Subnet1
      SecurityGroupIds:
        - !Ref SGWeb
```

#### MechCloud Desired State (Converted)

```yaml
resources:
  - type: aws_ec2_vpc
    name: vpc1
    props:
      cidr_block: "10.2.0.0/16"
    resources:
      - type: aws_ec2_security_group
        name: sg_web
        props:
          group_description: "Allow HTTP from internet"
          security_group_ingress:
            - ip_protocol: tcp
              from_port: 80
              to_port: 80
              cidr_ip: "0.0.0.0/0"
      - type: aws_ec2_security_group
        name: sg_service
        props:
          group_description: "Allow app traffic only from web1 SG"
          security_group_ingress:
            - ip_protocol: tcp
              from_port: 8080
              to_port: 8080
              source_security_group_id: "ref:sg_web"
      - type: aws_ec2_subnet
        name: subnet1
        props:
          cidr_block: "10.2.1.0/24"
        resources:
          - type: aws_ec2_instance
            name: web1
            props:
              image_id: "ami-0bc691261a82b32bc"
              instance_type: "t3.micro"
              security_group_ids:
                - "ref:vpc1/sg_web"
          - type: aws_ec2_instance
            name: service1
            props:
              image_id: "ami-0bc691261a82b32bc"
              instance_type: "t3.small"
              security_group_ids:
                - "ref:vpc1/sg_service"
```

This template contains two security groups (`sg_web` and `sg_service`) that are declared together under the same parent resource (`vpc1`). `sg_service` needs to *refer to the ID* of `sg_web` in its ingress rule (AWS expects an actual security-group ID like `sg-0123456789`). Because that ID is only known once the security group is created, MechCloud lets you refer to the other resource by name using the special `ref:<name>` syntax.

The EC2 instances (`web1` and `service1`) are nested under `subnet1`, which is a different `resources:` block (a different parent) than the security groups. They therefore cannot use the short `ref:sg_name` form (which resolves only among siblings in the same `resources:` block). Instead they must use the **full relative path** to the security group so MechCloud can uniquely locate it across the hierarchy:

* `source_security_group_id: "ref:sg_web"` — used *inside* `sg_service` because `sg_web` and `sg_service` are siblings under `vpc1`; this references `sg_web`’s ID for the security-group ingress rule.
* `security_group_ids: ["ref:vpc1/sg_web"]` and `["ref:vpc1/sg_service"]` — used on the EC2 instances because the instances live under `vpc1/subnet1`. The full path (`vpc1/sg_web`) tells MechCloud exactly which security-group resource to resolve and then substitute its actual ID into the instance’s `SecurityGroupIds` property.

In short: `ref:<name>` (short form) points to a sibling resource in the same `resources:` block; `ref:<full_path>` (path form) is required when the target resource lives under a different parent. MechCloud resolves either form at plan/apply time to the underlying AWS ID that CloudFormation/AWS expects.

### Conversion Rules

> **Tip**: Use hyphens in names for readability (e.g., `my-vpc`), but avoid spaces or special characters.

| CloudFormation                                      | → | MechCloud                                                 |
| --------------------------------------------------- | - | --------------------------------------------------------- |
| `Type: AWS::EC2::VPC`                               | → | `type: aws_ec2_vpc`                                       |
| `CidrBlock`                                         | → | `cidr_block`                                              |
| `VpcId: !Ref MyVPC`                                 | → | **Remove** — use nesting                                  |
| Logical ID `MyVPC`                                  | → | `name: my-vpc`                                            |
| `SecurityGroupIds: [!Ref MySG]`                     | → | `security_group_ids: ["ref:my-sg"]`                       |
| `SecurityGroupIngress` `SourceSecurityGroupId`      | → | `security_group_ingress` `source_security_group_id`       |
| `SecurityGroupIngress` `SourceSecurityGroupOwnerId` | → | `security_group_ingress` `source_security_group_owner_id` |

Notes and clarifications for the example template

* `snake_case` is mandatory in MechCloud YAML. MechCloud translates these property names to the AWS/CloudFormation property names when making provider calls (for example `source_security_group_id` → `SourceSecurityGroupId`).
* There are two kinds of `ref:` forms used in the example:
  * **Short name (sibling) form:** `ref:<name>` — used when one resource needs the ID of another resource that is declared **together under the same parent**. In the example both `sg_web` and `sg_service` are siblings under `vpc1`, so `sg_service` uses:

    ```yaml
    source_security_group_id: "ref:sg_web"
    ```

    MechCloud resolves `ref:sg_web` at plan/apply time to the actual security-group ID (for AWS).
  * **Full-path (non-sibling) form:** `ref:<relative_path>` — used when the referencing resource is in a different part of the hierarchy (different parent). In the example EC2 instances live under `vpc1/subnet1`, so they must reference the security groups by full relative path:

    ```yaml
    security_group_ids:
      - "ref:vpc1/sg_web"
      - "ref:vpc1/sg_service"
    ```

    The full path disambiguates the target resource for MechCloud so it can resolve it to the AWS GroupId.
* Use `source_security_group_owner_id` when referencing a security group that belongs to a different AWS account (this maps to CloudFormation `SourceSecurityGroupOwnerId`).
* Always ensure the `ref:` target resolves to a security-group **resource** (so MechCloud can substitute the runtime GroupId). If a `ref:` points to a non-existent path/name, plan will fail with an unresolved reference error.

### **Resource Paths (Must Be Unique)**

Each resource gets a **unique relative path** based on its nesting within the hierarchy.\
This path allows MechCloud to resolve references (`ref:`) precisely at plan and apply time.

| Resource                 | Path                    |
| ------------------------ | ----------------------- |
| VPC                      | `vpc1`                  |
| Security Group — Web     | `vpc1/sg_web`           |
| Security Group — Service | `vpc1/sg_service`       |
| Subnet                   | `vpc1/subnet1`          |
| Instance — Web           | `vpc1/subnet1/web1`     |
| Instance — Service       | `vpc1/subnet1/service1` |

Each resource’s path must be unique across the entire desired state file.

### Resource Id Aliases

Passing AMI ids and other resource identifiers, generated by a cloud provider, in the desired state is a huge pain. So you will first go to console or use cli and find out unique id of a cloud resource before using a child or linked resource. To simply this we support resource id aliases where you attached a special tag `mc-alias` to the target resource with a value of your choice and then use the value in a special format along with a predefined prefix for resource type. For example, instead of specifying AMI Ids, which vary by region, you can simply tag an AMI (e.g ubuntu server 24.04 for arm64) with `arm64_ubuntu_24_04` alias in all the regions and then use this value in the desired state, which will be region agnostic, as follows -

```
resources:
  - name: test-t4g-small
    type: aws_ec2_instance
    props:
      instance_type: "t4g.small"
      image_id: {{Image|arm64_ubuntu_24_04}}
```

resource id aliases are only supported for AWS AMIs at this moment and more resource types will be supported in the future -

| Provider | Resource Type              | Prefix |
| -------- | -------------------------- | ------ |
| AWS      |                            |        |
|          | Amazon Machine Image (AMI) | Image  |

### Next Steps

1. **Select Execution Context**\
   At the top of the MechCloud console, select the execution context for your operation:
   * **Team** — e.g., `Academy (User 2)`
   * **Cloud Provider** — e.g., `AWS`
   * **Cloud Account** — e.g., `Academy`
   * **Region** — e.g., `Europe (Ireland)`
   * **Context** — e.g., `app1-dev1`
2. **Generate the Plan**\
   Click **Plan** to generate an execution plan.\
   MechCloud compares the desired state (defined in YAML) with the actual AWS infrastructure and determines which resources require changes.
3. **Review Plan Actions**\
   Each resource in the plan shows an `action` indicating how MechCloud will reconcile it:
   * `create` — the resource does not exist and will be created.
   * `update` — the resource exists but properties differ and will be updated.
   * `delete` — the resource exists but is no longer in the desired state and will be removed.
   * `recreate` — the resource will be deleted and then created again (for non-updatable property changes).
   * `none` — the resource is already in the desired state; no action required.
4. **Apply the Plan (When Changes Exist)**
   * The **Apply** button becomes visible **only when** the plan includes at least one resource whose `action` is not `none`.
   * Click **Apply** to provision or update resources as per the plan.
   * MechCloud will execute the required operations and bring your AWS environment to match the desired state.
5. **Verify Post-Apply State**
   * Once the apply completes, click **Plan** again to re-run the comparison.
   * If all resources now display `action: none`, the desired and actual states are in sync.
   * In this case, the **Apply** button will not be visible because no further changes are required.
6. **Handle Errors (If Any)**
   * If any errors occur during the apply step, re-run **Plan** to refresh dependencies and reconcile state.
   * Then click **Apply** again to retry provisioning with the updated plan.
