Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/validation-massedcompute.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Massed Compute Validation Tests

on:
workflow_dispatch:
# Run explicitly from the Actions UI, GitHub CLI, or API.

jobs:
massedcompute-validation:
name: Massed Compute Provider Validation
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version-file: 'go.mod'

- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-

- name: Install dependencies
run: make deps

- name: Run Massed Compute validation tests
env:
MASSED_COMPUTE_API_TOKEN: ${{ secrets.MASSED_COMPUTE_API_TOKEN }}
TEST_PRIVATE_KEY_BASE64: ${{ secrets.TEST_PRIVATE_KEY_BASE64 }}
TEST_PUBLIC_KEY_BASE64: ${{ secrets.TEST_PUBLIC_KEY_BASE64 }}
VALIDATION_TEST: true
run: |
cd v1/providers/massedcompute
go test -v -short=false -timeout=30m ./...

- name: Upload test results
uses: actions/upload-artifact@v4
if: always()
with:
name: massedcompute-validation-results
path: |
v1/providers/massedcompute/coverage.out
25 changes: 25 additions & 0 deletions v1/providers/massedcompute/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
SPEC_VERSION ?= v1.0.0
SPEC_FILE ?= openapi-${SPEC_VERSION}.yaml
SPEC_FILE_FINAL ?= openapi-${SPEC_VERSION}.final.yaml
SPEC_PATCH ?= openapi-${SPEC_VERSION}.patch
OUTPUT_DIR ?= massedcompute

.PHONY: finalize-massedcompute-openapi generate-massedcompute-client
finalize-massedcompute-openapi:
cp ${SPEC_FILE} ${SPEC_FILE_FINAL}
patch -s -F 0 ${SPEC_FILE_FINAL} ${SPEC_PATCH}

generate-massedcompute-client: finalize-massedcompute-openapi
rm -rf gen/${OUTPUT_DIR}
mkdir -p gen/${OUTPUT_DIR}
docker run --rm -v "${CURDIR}:/local" openapitools/openapi-generator-cli:v7.8.0 generate \
--additional-properties disallowAdditionalPropertiesIfNotPresent=false \
-i /local/${SPEC_FILE_FINAL} \
-g go \
--git-user-id brevdev \
--git-repo-id cloud \
-o /local/gen/${OUTPUT_DIR}
find gen/${OUTPUT_DIR} -name "*.go" -type f -exec sed -i.bak 's|openapiclient "github.com/brevdev/cloud"|openapiclient "github.com/brevdev/cloud/v1/providers/massedcompute/gen/massedcompute"|g' {} \; && find gen/${OUTPUT_DIR} -name "*.go.bak" -delete
find gen/${OUTPUT_DIR} -name "*.md" -type f -exec sed -i.bak 's/[[:space:]]*$$//' {} \; && find gen/${OUTPUT_DIR} -name "*.md.bak" -delete
gofmt -s -w gen/${OUTPUT_DIR}
rm -f gen/${OUTPUT_DIR}/go.mod gen/${OUTPUT_DIR}/go.sum
23 changes: 23 additions & 0 deletions v1/providers/massedcompute/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Massed Compute Provider

This package implements the minimal Brev Cloud v1 surface for Massed Compute.

The SDK exposes and reports a single `massedcompute` location, which maps to the API's `any` launch region.

## Generated API client

The generated client is committed under `gen/massedcompute`. Regenerate it from the version-pinned Massed Compute OpenAPI specification with:

```sh
make -C v1/providers/massedcompute generate-massedcompute-client
```

`openapi-v1.0.0.yaml` is the unmodified vendor specification. At generation time, the version-pinned `openapi-v1.0.0.patch` produces `openapi-v1.0.0.final.yaml` with the few repairs needed for strict validation: matching the single-instance operation to its actual `runningInstances` response envelope, declaring the omitted instance `{uuid}` parameter, correcting the launch request's required fields, relocating two request examples, and repairing two misplaced terminate-response fields. Patch application fails if a future vendor document no longer matches these exact locations.

## Live validation

Set `MASSED_COMPUTE_API_TOKEN` and optionally `MASSED_COMPUTE_API_URL`, then run:

```sh
go test -run TestValidationFunctions ./v1/providers/massedcompute
```
145 changes: 145 additions & 0 deletions v1/providers/massedcompute/bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package massedcompute

import (
"encoding/base64"
"fmt"
"net"
"strings"

v1 "github.com/brevdev/cloud/v1"
)

const (
dockerFirewallScriptPath = "/usr/local/sbin/brev-apply-docker-firewall.sh"
dockerFirewallDropInPath = "/etc/systemd/system/docker.service.d/10-brev-firewall.conf"
)

func buildStartupCommand(rules v1.FirewallRules) (string, error) {
script, err := buildStartupScript(rules)
if err != nil {
return "", err
}
encodedScript := base64.StdEncoding.EncodeToString([]byte(script))
return "printf %s '" + encodedScript + "' | base64 --decode | sudo -n bash", nil
}

func buildStartupScript(rules v1.FirewallRules) (string, error) {
ufwRules, dockerRules, err := firewallRuleCommands(rules.IngressRules)
if err != nil {
return "", err
}

var script strings.Builder
script.WriteString(`#!/bin/bash
set -u

passwd --lock ubuntu

if ! command -v ufw >/dev/null 2>&1; then
apt-get update -y
DEBIAN_FRONTEND=noninteractive apt-get install -y ufw iptables
fi

mkdir -p /usr/local/sbin /etc/systemd/system/docker.service.d
cat > ` + dockerFirewallScriptPath + ` <<'BREV_FIREWALL'
#!/bin/sh
iptables -N DOCKER-USER 2>/dev/null || true
iptables -F DOCKER-USER || true
iptables -A DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A DOCKER-USER -i docker0 ! -o docker0 -j ACCEPT
iptables -A DOCKER-USER -i br+ ! -o br+ -j ACCEPT
iptables -A DOCKER-USER -i cni+ ! -o cni+ -j ACCEPT
iptables -A DOCKER-USER -i cali+ ! -o cali+ -j ACCEPT
iptables -A DOCKER-USER -i docker0 -o docker0 -j ACCEPT
iptables -A DOCKER-USER -i br+ -o br+ -j ACCEPT
iptables -A DOCKER-USER -i cni+ -o cni+ -j ACCEPT
iptables -A DOCKER-USER -i cali+ -o cali+ -j ACCEPT
iptables -A DOCKER-USER -i lo -j ACCEPT
iptables -A DOCKER-USER -i wt0 -j ACCEPT
`)
for _, command := range dockerRules {
script.WriteString(command)
script.WriteByte('\n')
}
script.WriteString(`iptables -A DOCKER-USER -j DROP
exit 0
BREV_FIREWALL
chmod 0755 ` + dockerFirewallScriptPath + `

cat > ` + dockerFirewallDropInPath + ` <<'BREV_DROP_IN'
[Service]
ExecStartPost=-` + dockerFirewallScriptPath + `
BREV_DROP_IN

systemctl daemon-reload || true
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
`)
for _, command := range ufwRules {
script.WriteString(command)
script.WriteByte('\n')
}
script.WriteString(`ufw --force enable
` + dockerFirewallScriptPath + ` || true
`)
return script.String(), nil
}

func firewallRuleCommands(rules []v1.FirewallRule) ([]string, []string, error) {
var ufwCommands []string
var dockerCommands []string
for _, rule := range rules {
if rule.FromPort < 1 || rule.ToPort > 65535 || rule.FromPort > rule.ToPort {
return nil, nil, fmt.Errorf("invalid firewall port range %d-%d", rule.FromPort, rule.ToPort)
}

sources := rule.IPRanges
if len(sources) == 0 {
sources = []string{"0.0.0.0/0"}
}
for _, source := range sources {
ip, network, err := net.ParseCIDR(source)
if err != nil {
return nil, nil, fmt.Errorf("invalid firewall CIDR %q: %w", source, err)
}
if ip.To4() == nil {
return nil, nil, fmt.Errorf("IPv6 firewall CIDR %q is not supported", source)
}
source = network.String()

if rule.FromPort == rule.ToPort {
ufwCommands = append(ufwCommands, fmt.Sprintf(
"ufw allow from %s to any port %d",
source,
rule.FromPort,
))
} else {
for _, protocol := range []string{"tcp", "udp"} {
ufwCommands = append(ufwCommands, fmt.Sprintf(
"ufw allow from %s to any port %d:%d proto %s",
source,
rule.FromPort,
rule.ToPort,
protocol,
))
}
}

portSpec := fmt.Sprintf("%d", rule.FromPort)
if rule.FromPort != rule.ToPort {
portSpec = fmt.Sprintf("%d:%d", rule.FromPort, rule.ToPort)
}
for _, protocol := range []string{"tcp", "udp"} {
dockerCommands = append(dockerCommands, fmt.Sprintf(
"iptables -A DOCKER-USER -s %s -p %s --dport %s -j ACCEPT",
source,
protocol,
portSpec,
))
}
}
}
return ufwCommands, dockerCommands, nil
}
19 changes: 19 additions & 0 deletions v1/providers/massedcompute/capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package massedcompute

import (
"context"

v1 "github.com/brevdev/cloud/v1"
)

func getCapabilities() v1.Capabilities {
return v1.Capabilities{
v1.CapabilityCreateInstance,
v1.CapabilityTerminateInstance,
v1.CapabilityCreateTerminateInstance,
}
}

func (c *MassedComputeClient) GetCapabilities(_ context.Context) (v1.Capabilities, error) {
return getCapabilities(), nil
}
Loading
Loading