Skip to content

Commit 21525cc

Browse files
committed
feat: Addeed initial version for spec driven
This commit creates the first struct for spec driven based in current content offered by mapt Co-authored-by: Claude <noreply@anthropic.com> Signed-off-by: Adrian Riobo <ariobolo@redhat.com>
1 parent 6ad3164 commit 21525cc

49 files changed

Lines changed: 5011 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ manifest.json
55
ami-id
66
Pulumi*.yaml
77
/tools/bin/**
8-
!/tools/bin/.gitkeep
8+
!/tools/bin/.gitkeep
9+
.claude

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@specs/project-context.md

specs/api/aws/allocation.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# API: Allocation (AWS)
2+
3+
> Concept: [specs/api/concepts/allocation.md](../concepts/allocation.md)
4+
5+
**Package:** `github.com/redhat-developer/mapt/pkg/provider/aws/modules/allocation`
6+
7+
Single entry point for resolving where and on what instance type a target will run.
8+
All AWS EC2 action `Create()` functions call this before any Pulumi stack is touched.
9+
10+
---
11+
12+
## Types
13+
14+
### `AllocationArgs`
15+
16+
> `ComputeRequestArgs` and `SpotArgs` are cross-provider types — see `specs/api/provider-interfaces.md`.
17+
18+
```go
19+
type AllocationArgs struct {
20+
ComputeRequest *cr.ComputeRequestArgs // required: hardware constraints
21+
Prefix *string // required: used to name the spot stack
22+
AMIProductDescription *string // optional: e.g. "Linux/UNIX" — used for spot price queries
23+
AMIName *string // optional: scopes spot search to AMI availability
24+
Spot *spotTypes.SpotArgs // nil = on-demand; non-nil = spot evaluation
25+
}
26+
```
27+
28+
### `AllocationResult`
29+
30+
```go
31+
type AllocationResult struct {
32+
Region *string // AWS region to deploy into
33+
AZ *string // availability zone within that region
34+
SpotPrice *float64 // nil when on-demand; set when spot was selected
35+
InstanceTypes []string // one or more compatible instance type strings
36+
}
37+
```
38+
39+
---
40+
41+
## Functions
42+
43+
### `Allocation`
44+
45+
```go
46+
func Allocation(mCtx *mc.Context, args *AllocationArgs) (*AllocationResult, error)
47+
```
48+
49+
**Spot path** (`args.Spot != nil && args.Spot.Spot == true`):
50+
- Creates or reuses a `spotOption-<projectName>` Pulumi stack
51+
- Queries spot prices across eligible regions; selects best region/AZ/price
52+
- Idempotent: if the stack already exists, returns its saved outputs without re-querying
53+
- Returns `AllocationResult` with all four fields set
54+
55+
**On-demand path** (`args.Spot == nil` or `args.Spot.Spot == false`):
56+
- Uses `mCtx.TargetHostingPlace()` as the region (set from provider default)
57+
- Iterates AZs until one supports the required instance types
58+
- Returns `AllocationResult` with `SpotPrice == nil`
59+
60+
**Error:** returns `ErrNoSupportedInstanceTypes` if no AZ in the region supports the requested types.
61+
62+
---
63+
64+
## Usage Pattern
65+
66+
```go
67+
// In every AWS action Create():
68+
r.allocationData, err = allocation.Allocation(mCtx, &allocation.AllocationArgs{
69+
Prefix: &args.Prefix,
70+
ComputeRequest: args.ComputeRequest,
71+
AMIProductDescription: &amiProduct, // constant in the action's constants.go
72+
Spot: args.Spot,
73+
})
74+
75+
// Then pass results into the deploy function:
76+
// r.allocationData.Region → NetworkArgs.Region, ComputeRequest credential region
77+
// r.allocationData.AZ → NetworkArgs.AZ
78+
// r.allocationData.InstanceTypes → ComputeRequest.InstaceTypes
79+
// r.allocationData.SpotPrice → ComputeRequest.SpotPrice (when non-nil)
80+
```
81+
82+
---
83+
84+
## Known Gaps
85+
86+
- `spot.Destroy()` uses `aws.DefaultCredentials` (not region-scoped); verify this is correct
87+
when the selected spot region differs from the default AWS region
88+
- No re-evaluation of spot selection when the persisted region becomes significantly more expensive
89+
between runs (by design — idempotency wins; worth documenting in user docs)
90+
91+
---
92+
93+
## When to Extend This API
94+
95+
Open a spec under `specs/features/aws/` and update this file when:
96+
- Adding a new allocation strategy (e.g. reserved instances, on-demand with fallback to spot)
97+
- Adding a new field to `AllocationArgs` that all targets would benefit from
98+
- Changing the idempotency behaviour of the spot stack

specs/api/aws/bastion.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# API: Bastion
2+
3+
**Package:** `github.com/redhat-developer/mapt/pkg/provider/aws/modules/bastion`
4+
5+
Creates a bastion host in the public subnet of an airgap network. Called automatically by
6+
`network.Create()` when `Airgap=true` — action code never calls bastion directly during deploy.
7+
8+
Action code calls `bastion.WriteOutputs()` in `manageResults()` when airgap is enabled.
9+
10+
---
11+
12+
## Types
13+
14+
### `BastionArgs`
15+
16+
```go
17+
type BastionArgs struct {
18+
Prefix string
19+
VPC *ec2.Vpc
20+
Subnet *ec2.Subnet // must be the PUBLIC subnet, not the target subnet
21+
}
22+
```
23+
24+
### `BastionResult`
25+
26+
```go
27+
type BastionResult struct {
28+
Instance *ec2.Instance
29+
PrivateKey *tls.PrivateKey
30+
Usarname string // note: typo in source — "Usarname" not "Username"
31+
Port int // always 22
32+
}
33+
```
34+
35+
---
36+
37+
## Functions
38+
39+
### `Create`
40+
41+
```go
42+
func Create(ctx *pulumi.Context, mCtx *mc.Context, args *BastionArgs) (*BastionResult, error)
43+
```
44+
45+
Called internally by `network.Create()`. Not called directly from action code.
46+
47+
Creates:
48+
- Amazon Linux 2 `t2.small` instance in the public subnet
49+
- Keypair for SSH access
50+
- Security group allowing SSH ingress from `0.0.0.0/0`
51+
52+
Exports to Pulumi stack:
53+
- `<prefix>-bastion_id_rsa`
54+
- `<prefix>-bastion_username`
55+
- `<prefix>-bastion_host`
56+
57+
### `WriteOutputs`
58+
59+
```go
60+
func WriteOutputs(stackResult auto.UpResult, prefix string, destinationFolder string) error
61+
```
62+
63+
Writes the three bastion stack outputs to files in `destinationFolder`:
64+
65+
| Stack output key | Output filename |
66+
|---|---|
67+
| `<prefix>-bastion_id_rsa` | `bastion_id_rsa` |
68+
| `<prefix>-bastion_username` | `bastion_username` |
69+
| `<prefix>-bastion_host` | `bastion_host` |
70+
71+
---
72+
73+
## Usage Pattern
74+
75+
```go
76+
// In deploy(): bastion is returned as part of NetworkResult — no direct call needed
77+
nw, err := network.Create(ctx, mCtx, &network.NetworkArgs{Airgap: true, ...})
78+
// nw.Bastion is populated automatically
79+
80+
// Pass to Readiness() so SSH goes through the bastion:
81+
c.Readiness(ctx, cmd, prefix, id, privateKey, username, nw.Bastion, deps)
82+
83+
// In manageResults(): write bastion files alongside target files
84+
func manageResults(mCtx *mc.Context, stackResult auto.UpResult, prefix *string, airgap *bool) error {
85+
if *airgap {
86+
if err := bastion.WriteOutputs(stackResult, *prefix, mCtx.GetResultsOutputPath()); err != nil {
87+
return err
88+
}
89+
}
90+
return output.Write(stackResult, mCtx.GetResultsOutputPath(), results)
91+
}
92+
```
93+
94+
---
95+
96+
## Bastion Instance Spec (fixed, not configurable)
97+
98+
| Property | Value |
99+
|---|---|
100+
| AMI | Amazon Linux 2 (`amzn2-ami-hvm-*-x86_64-ebs`) |
101+
| Instance type | `t2.small` |
102+
| Disk | 100 GiB |
103+
| SSH user | `ec2-user` |
104+
| SSH port | 22 |
105+
106+
---
107+
108+
## When to Extend This API
109+
110+
Open a spec under `specs/features/aws/` and update this file when:
111+
- Making bastion instance type or disk size configurable
112+
- Adding bastion support to Azure targets
113+
- Adding support for Session Manager as an alternative to bastion SSH

specs/api/aws/compute.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# API: Compute (AWS EC2)
2+
3+
> Concept: [specs/api/concepts/compute.md](../concepts/compute.md)
4+
5+
**Package:** `github.com/redhat-developer/mapt/pkg/provider/aws/modules/ec2/compute`
6+
7+
Creates the EC2 instance (on-demand) or Auto Scaling Group (spot). Always the last Pulumi
8+
resource created in a `deploy()` function, after networking, keypair, and security groups.
9+
10+
---
11+
12+
## Types
13+
14+
### `ComputeRequest`
15+
16+
```go
17+
type ComputeRequest struct {
18+
MCtx *mc.Context
19+
Prefix string
20+
ID string // component ID — used in resource naming
21+
VPC *ec2.Vpc // from network.NetworkResult.Vpc
22+
Subnet *ec2.Subnet // from network.NetworkResult.Subnet
23+
Eip *ec2.Eip // from network.NetworkResult.Eip
24+
LB *lb.LoadBalancer // from network.NetworkResult.LoadBalancer; nil = on-demand
25+
LBTargetGroups []int // TCP ports to register as LB target groups (e.g. []int{22, 3389})
26+
AMI *ec2.LookupAmiResult
27+
KeyResources *keypair.KeyPairResources
28+
SecurityGroups pulumi.StringArray
29+
InstaceTypes []string // from AllocationResult.InstanceTypes
30+
InstanceProfile *iam.InstanceProfile // optional — required by SNC for SSM access
31+
DiskSize *int // nil uses the module default (200 GiB)
32+
Airgap bool
33+
Spot bool // true when AllocationResult.SpotPrice != nil
34+
SpotPrice float64 // only read when Spot=true
35+
UserDataAsBase64 pulumi.StringPtrInput // cloud-init or PowerShell userdata
36+
DependsOn []pulumi.Resource // explicit Pulumi dependencies
37+
}
38+
```
39+
40+
### `Compute`
41+
42+
```go
43+
type Compute struct {
44+
Instance *ec2.Instance // set when Spot=false
45+
AutoscalingGroup *autoscaling.Group // set when Spot=true
46+
Eip *ec2.Eip
47+
LB *lb.LoadBalancer
48+
Dependencies []pulumi.Resource // pass to Readiness() and RunCommand()
49+
}
50+
```
51+
52+
---
53+
54+
## Functions
55+
56+
### `NewCompute`
57+
58+
```go
59+
func (r *ComputeRequest) NewCompute(ctx *pulumi.Context) (*Compute, error)
60+
```
61+
62+
- `Spot=false`: creates `ec2.Instance` with direct EIP association
63+
- `Spot=true`: creates `ec2.LaunchTemplate` + `autoscaling.Group` with mixed instances policy, forced spot, capacity-optimized allocation strategy; registers LB target groups
64+
65+
### `Readiness`
66+
67+
```go
68+
func (c *Compute) Readiness(
69+
ctx *pulumi.Context,
70+
cmd string, // command.CommandCloudInitWait or command.CommandPing
71+
prefix, id string,
72+
mk *tls.PrivateKey,
73+
username string,
74+
b *bastion.BastionResult, // nil when not airgap
75+
dependencies []pulumi.Resource,
76+
) error
77+
```
78+
79+
Runs `cmd` over SSH on the instance. Blocks Pulumi until it succeeds (timeout: 40 minutes).
80+
Pass `c.Dependencies` as `dependencies`.
81+
82+
### `RunCommand`
83+
84+
```go
85+
func (c *Compute) RunCommand(
86+
ctx *pulumi.Context,
87+
cmd string,
88+
loggingCmdStd bool, // compute.LoggingCmdStd or compute.NoLoggingCmdStd
89+
prefix, id string,
90+
mk *tls.PrivateKey,
91+
username string,
92+
b *bastion.BastionResult,
93+
dependencies []pulumi.Resource,
94+
) (*remote.Command, error)
95+
```
96+
97+
Like `Readiness` but returns the command resource for use as a dependency in subsequent steps.
98+
Used by SNC to chain SSH → cluster ready → CA rotated → fetch kubeconfig.
99+
100+
### `GetHostDnsName`
101+
102+
```go
103+
func (c *Compute) GetHostDnsName(public bool) pulumi.StringInput
104+
```
105+
106+
Returns `LB.DnsName` when LB is set, otherwise `Eip.PublicDns` (public=true) or `Eip.PrivateDns` (public=false).
107+
Export this as `<prefix>-host`.
108+
109+
### `GetHostIP`
110+
111+
```go
112+
func (c *Compute) GetHostIP(public bool) pulumi.StringOutput
113+
```
114+
115+
Returns `Eip.PublicIp` or `Eip.PrivateIp`. Used by SNC (needs IP not DNS for kubeconfig replacement).
116+
117+
---
118+
119+
## Readiness Commands
120+
121+
| Constant | Value | When to use |
122+
|---|---|---|
123+
| `command.CommandCloudInitWait` | `sudo cloud-init status --long --wait \|\| [[ $? -eq 2 \|\| $? -eq 0 ]]` | Linux targets with cloud-init |
124+
| `command.CommandPing` | `echo ping` | Windows targets (no cloud-init) |
125+
126+
---
127+
128+
## Usage Pattern
129+
130+
```go
131+
cr := compute.ComputeRequest{
132+
MCtx: r.mCtx,
133+
Prefix: *r.prefix,
134+
ID: awsTargetID,
135+
VPC: nw.Vpc,
136+
Subnet: nw.Subnet,
137+
Eip: nw.Eip,
138+
LB: nw.LoadBalancer,
139+
LBTargetGroups: []int{22}, // add 3389 for Windows
140+
AMI: ami,
141+
KeyResources: keyResources,
142+
SecurityGroups: securityGroups,
143+
InstaceTypes: r.allocationData.InstanceTypes,
144+
DiskSize: &diskSize, // constant in constants.go
145+
Airgap: *r.airgap,
146+
UserDataAsBase64: udB64,
147+
}
148+
if r.allocationData.SpotPrice != nil {
149+
cr.Spot = true
150+
cr.SpotPrice = *r.allocationData.SpotPrice
151+
}
152+
c, err := cr.NewCompute(ctx)
153+
154+
ctx.Export(fmt.Sprintf("%s-%s", *r.prefix, outputHost), c.GetHostDnsName(!*r.airgap))
155+
156+
return c.Readiness(ctx, command.CommandCloudInitWait,
157+
*r.prefix, awsTargetID,
158+
keyResources.PrivateKey, amiUserDefault,
159+
nw.Bastion, c.Dependencies)
160+
```
161+
162+
---
163+
164+
## When to Extend This API
165+
166+
Open a spec under `specs/features/aws/` and update this file when:
167+
- Adding support for additional storage volumes
168+
- Adding support for instance store (NVMe) configuration
169+
- Exposing health check grace period as configurable (currently hardcoded at 1200s)
170+
- Adding on-demand with spot fallback (noted as TODO in source)

0 commit comments

Comments
 (0)