# 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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.mechcloud.io/cloud-computing/stateless-iac/aws/authoring-desired-state.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
