diff --git a/.air.toml b/.air.toml new file mode 100644 index 00000000..d30c8963 --- /dev/null +++ b/.air.toml @@ -0,0 +1,45 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ./cmd/api" + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata", "bin", "scripts", "data", "kernel"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html", "yaml"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true + diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..0e0d628f --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +JWT_SECRET='your-secret-key-here' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..36e6b71d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,25 @@ +name: Test + +on: + push: {} + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Install dependencies + run: go mod download + + - name: Run tests + run: make test + + - name: Build + run: make build + diff --git a/.gitignore b/.gitignore index fb13fa28..1d7185cd 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,9 @@ initramfs-overlay/** data/** *.raw *.sock +kernel +kernel/** +bin/** +.env +tmp +tmp/** diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..94a3204c --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +SHELL := /bin/bash +.PHONY: oapi-generate generate-wire generate-all dev build test install-tools + +# Directory where local binaries will be installed +BIN_DIR ?= $(CURDIR)/bin + +$(BIN_DIR): + mkdir -p $(BIN_DIR) + +# Local binary paths +OAPI_CODEGEN ?= $(BIN_DIR)/oapi-codegen +AIR ?= $(BIN_DIR)/air +WIRE ?= $(BIN_DIR)/wire + +# Install oapi-codegen +$(OAPI_CODEGEN): | $(BIN_DIR) + GOBIN=$(BIN_DIR) go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest + +# Install air for hot reload +$(AIR): | $(BIN_DIR) + GOBIN=$(BIN_DIR) go install github.com/air-verse/air@latest + +# Install wire for dependency injection +$(WIRE): | $(BIN_DIR) + GOBIN=$(BIN_DIR) go install github.com/google/wire/cmd/wire@latest + +install-tools: $(OAPI_CODEGEN) $(AIR) $(WIRE) + +# Generate Go code from OpenAPI spec +oapi-generate: $(OAPI_CODEGEN) + @echo "Generating Go code from OpenAPI spec..." + $(OAPI_CODEGEN) -config ./oapi-codegen.yaml ./openapi.yaml + @echo "Formatting generated code..." + go fmt ./lib/oapi/oapi.go + +# Generate wire dependency injection code +generate-wire: $(WIRE) + @echo "Generating wire code..." + cd ./cmd/api && $(WIRE) + +# Generate all code +generate-all: oapi-generate generate-wire + +# Build the binary +build: | $(BIN_DIR) + go build -o $(BIN_DIR)/hypeman ./cmd/api + +# Run in development mode with hot reload +dev: $(AIR) + $(AIR) -c .air.toml + +# Run tests +test: + go test -v -timeout 30s ./... + +# Clean generated files and binaries +clean: + rm -rf $(BIN_DIR) + rm -f lib/oapi/oapi.go + diff --git a/README.md b/README.md index 4e2162fd..419c68a9 100644 --- a/README.md +++ b/README.md @@ -1,125 +1,68 @@ -# Cloud Hypervisor POC +# Hypeman -Proof of concept for running 10 Chromium VMs simultaneously using Cloud Hypervisor with disk-based overlays, config disks, networking isolation, and standby/restore functionality. +[![Test](https://github.com/onkernel/hypeman/actions/workflows/test.yml/badge.svg)](https://github.com/onkernel/hypeman/actions/workflows/test.yml) -## Prerequisites +Run containerized workloads in VMs, powered by [Cloud Hypervisor](https://github.com/cloud-hypervisor/cloud-hypervisor). -Install cloud-hypervisor by [installing the pre-built binaries](https://www.cloudhypervisor.org/docs/prologue/quick-start/#use-pre-built-binaries). Make sure `ch-remote` and `cloud-hypervisor` are in path. +## Getting Started -```bash -ch-remote --version -cloud-hypervisor --version -``` - -Tested with version `v48.0.0` - -Note: Requires `kernel-images-private` cloned to home directory with `iproute2` installed in the Chromium headful image. - -Also, `lsof` and `lz4` needs to be installed on the host - -``` -sudo apt-get install -y lsof lz4 -``` - -## Setup - -Build kernel, initrd, and rootfs with config disk support: +### Prerequisites +**Cloud Hypervisor** - [Installation guide](https://www.cloudhypervisor.org/docs/prologue/quick-start/#use-pre-built-binaries) ```bash -./scripts/build-initrd.sh -``` - -This creates: -- `data/system/vmlinux` - Linux kernel -- `data/system/initrd` - BusyBox init with disk-based overlay -- `data/images/chromium-headful/v1/rootfs.ext4` - Chromium rootfs (read-only, shared) - -Configure host network with bridge and guest isolation: - -```bash -./scripts/setup-host-network.sh -``` - -Create 10 VM configurations (IPs 192.168.100.10-19, isolated TAP devices, overlay disks, config disks): - -```bash -./scripts/setup-vms.sh +cloud-hypervisor --version # Verify +ch-remote --version ``` -## Running VMs - -Start all 10 VMs: - +**containerd** - [Installation guide](https://github.com/containerd/containerd/blob/main/docs/getting-started.md) ```bash -./scripts/start-all-vms.sh +containerd --version # Verify ``` -Check VM status: +**Go 1.25.4+** and **KVM** -```bash -./scripts/list-vms.sh -``` - -View VM logs: +### Configuration ```bash -./scripts/logs-vm.sh # Show last 100 lines -./scripts/logs-vm.sh -f # Follow logs +cp .env.example .env +# Edit .env and set JWT_SECRET ``` -SSH into a VM: +### Build ```bash -./scripts/ssh-vm.sh # Password: root +make build ``` +### Running the Server -Stop a VM: - +Start the server with hot-reload for development: ```bash -./scripts/stop-vm.sh -./scripts/stop-all-vms.sh # Stop all +make dev ``` +The server will start on port 8080 (configurable via `PORT` environment variable). -## Standby / Restore - -Standby a VM (pause, snapshot, delete VMM): - -```bash -./scripts/standby-vm.sh -``` - -Restore a VM from snapshot: +### Testing ```bash -./scripts/restore-vm.sh +make test ``` -## Networking +### Code Generation -Enable port forwarding for WebRTC access (localhost:8080-8089 → guest VMs): +After modifying `openapi.yaml`, regenerate the Go code: ```bash -./scripts/setup-port-forwarding.sh +make oapi-generate ``` -Connect to a VM: +After modifying dependency injection in `cmd/api/wire.go` or `lib/providers/providers.go`, regenerate wire code: ```bash -./scripts/connect-guest.sh +make generate-wire ``` -## Volumes - -Create a persistent volume: +Or generate everything at once: ```bash -./scripts/create-volume.sh +make generate-all ``` - -## Architecture - -- **Disk-based overlay**: Each VM has a 50GB sparse overlay disk on `/dev/vdb` (faster restore than tmpfs) -- **Config disk**: Each VM has a config disk on `/dev/vdc` with VM-specific settings (IP, MAC, envs) -- **Guest isolation**: VMs cannot communicate with each other (iptables + bridge_slave isolation) -- **Serial logging**: All VM output captured to `data/guests/guest-N/logs/console.log` -- **Shared rootfs**: Single read-only rootfs image shared across all VMs diff --git a/cmd/api/api/api.go b/cmd/api/api/api.go new file mode 100644 index 00000000..c2e8df58 --- /dev/null +++ b/cmd/api/api/api.go @@ -0,0 +1,35 @@ +package api + +import ( + "github.com/onkernel/hypeman/cmd/api/config" + "github.com/onkernel/hypeman/lib/images" + "github.com/onkernel/hypeman/lib/instances" + "github.com/onkernel/hypeman/lib/oapi" + "github.com/onkernel/hypeman/lib/volumes" +) + +// ApiService implements the oapi.StrictServerInterface +type ApiService struct { + Config *config.Config + ImageManager images.Manager + InstanceManager instances.Manager + VolumeManager volumes.Manager +} + +var _ oapi.StrictServerInterface = (*ApiService)(nil) + +// New creates a new ApiService +func New( + config *config.Config, + imageManager images.Manager, + instanceManager instances.Manager, + volumeManager volumes.Manager, +) *ApiService { + return &ApiService{ + Config: config, + ImageManager: imageManager, + InstanceManager: instanceManager, + VolumeManager: volumeManager, + } +} + diff --git a/cmd/api/api/api_test.go b/cmd/api/api/api_test.go new file mode 100644 index 00000000..305e6430 --- /dev/null +++ b/cmd/api/api/api_test.go @@ -0,0 +1,30 @@ +package api + +import ( + "context" + "testing" + + "github.com/onkernel/hypeman/cmd/api/config" + "github.com/onkernel/hypeman/lib/images" + "github.com/onkernel/hypeman/lib/instances" + "github.com/onkernel/hypeman/lib/volumes" +) + +// newTestService creates an ApiService for testing with temporary data directory +func newTestService(t *testing.T) *ApiService { + cfg := &config.Config{ + DataDir: t.TempDir(), + } + + return &ApiService{ + Config: cfg, + ImageManager: images.NewManager(cfg.DataDir), + InstanceManager: instances.NewManager(cfg.DataDir), + VolumeManager: volumes.NewManager(cfg.DataDir), + } +} + +func ctx() context.Context { + return context.Background() +} + diff --git a/cmd/api/api/health.go b/cmd/api/api/health.go new file mode 100644 index 00000000..139be32d --- /dev/null +++ b/cmd/api/api/health.go @@ -0,0 +1,15 @@ +package api + +import ( + "context" + + "github.com/onkernel/hypeman/lib/oapi" +) + +// GetHealth implements health check endpoint +func (s *ApiService) GetHealth(ctx context.Context, request oapi.GetHealthRequestObject) (oapi.GetHealthResponseObject, error) { + return oapi.GetHealth200JSONResponse{ + Status: oapi.Ok, + }, nil +} + diff --git a/cmd/api/api/images.go b/cmd/api/api/images.go new file mode 100644 index 00000000..470cee31 --- /dev/null +++ b/cmd/api/api/images.go @@ -0,0 +1,95 @@ +package api + +import ( + "context" + "errors" + + "github.com/onkernel/hypeman/lib/images" + "github.com/onkernel/hypeman/lib/logger" + "github.com/onkernel/hypeman/lib/oapi" +) + +// ListImages lists all images +func (s *ApiService) ListImages(ctx context.Context, request oapi.ListImagesRequestObject) (oapi.ListImagesResponseObject, error) { + log := logger.FromContext(ctx) + + imgs, err := s.ImageManager.ListImages(ctx) + if err != nil { + log.Error("failed to list images", "error", err) + return oapi.ListImages500JSONResponse{ + Code: "internal_error", + Message: "failed to list images", + }, nil + } + return oapi.ListImages200JSONResponse(imgs), nil +} + +// CreateImage creates a new image from an OCI reference +func (s *ApiService) CreateImage(ctx context.Context, request oapi.CreateImageRequestObject) (oapi.CreateImageResponseObject, error) { + log := logger.FromContext(ctx) + + img, err := s.ImageManager.CreateImage(ctx, *request.Body) + if err != nil { + switch { + case errors.Is(err, images.ErrAlreadyExists): + return oapi.CreateImage400JSONResponse{ + Code: "already_exists", + Message: "image already exists", + }, nil + default: + log.Error("failed to create image", "error", err, "name", request.Body.Name) + return oapi.CreateImage500JSONResponse{ + Code: "internal_error", + Message: "failed to create image", + }, nil + } + } + return oapi.CreateImage201JSONResponse(*img), nil +} + +// GetImage gets image details +func (s *ApiService) GetImage(ctx context.Context, request oapi.GetImageRequestObject) (oapi.GetImageResponseObject, error) { + log := logger.FromContext(ctx) + + img, err := s.ImageManager.GetImage(ctx, request.Id) + if err != nil { + switch { + case errors.Is(err, images.ErrNotFound): + return oapi.GetImage404JSONResponse{ + Code: "not_found", + Message: "image not found", + }, nil + default: + log.Error("failed to get image", "error", err, "id", request.Id) + return oapi.GetImage500JSONResponse{ + Code: "internal_error", + Message: "failed to get image", + }, nil + } + } + return oapi.GetImage200JSONResponse(*img), nil +} + +// DeleteImage deletes an image +func (s *ApiService) DeleteImage(ctx context.Context, request oapi.DeleteImageRequestObject) (oapi.DeleteImageResponseObject, error) { + log := logger.FromContext(ctx) + + err := s.ImageManager.DeleteImage(ctx, request.Id) + if err != nil { + switch { + case errors.Is(err, images.ErrNotFound): + return oapi.DeleteImage404JSONResponse{ + Code: "not_found", + Message: "image not found", + }, nil + default: + log.Error("failed to delete image", "error", err, "id", request.Id) + return oapi.DeleteImage500JSONResponse{ + Code: "internal_error", + Message: "failed to delete image", + }, nil + } + } + return oapi.DeleteImage204Response{}, nil +} + diff --git a/cmd/api/api/images_test.go b/cmd/api/api/images_test.go new file mode 100644 index 00000000..d948af9c --- /dev/null +++ b/cmd/api/api/images_test.go @@ -0,0 +1,35 @@ +package api + +import ( + "testing" + + "github.com/onkernel/hypeman/lib/oapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestListImages_Empty(t *testing.T) { + svc := newTestService(t) + + resp, err := svc.ListImages(ctx(), oapi.ListImagesRequestObject{}) + require.NoError(t, err) + + list, ok := resp.(oapi.ListImages200JSONResponse) + require.True(t, ok, "expected 200 response") + assert.Empty(t, list) +} + +func TestGetImage_NotFound(t *testing.T) { + svc := newTestService(t) + + resp, err := svc.GetImage(ctx(), oapi.GetImageRequestObject{ + Id: "non-existent", + }) + require.NoError(t, err) + + notFound, ok := resp.(oapi.GetImage404JSONResponse) + require.True(t, ok, "expected 404 response") + assert.Equal(t, "not_found", notFound.Code) + assert.Equal(t, "image not found", notFound.Message) +} + diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go new file mode 100644 index 00000000..82976bcb --- /dev/null +++ b/cmd/api/api/instances.go @@ -0,0 +1,226 @@ +package api + +import ( + "context" + "errors" + "strings" + + "github.com/onkernel/hypeman/lib/instances" + "github.com/onkernel/hypeman/lib/logger" + "github.com/onkernel/hypeman/lib/oapi" +) + +// ListInstances lists all instances +func (s *ApiService) ListInstances(ctx context.Context, request oapi.ListInstancesRequestObject) (oapi.ListInstancesResponseObject, error) { + log := logger.FromContext(ctx) + + insts, err := s.InstanceManager.ListInstances(ctx) + if err != nil { + log.Error("failed to list instances", "error", err) + return oapi.ListInstances500JSONResponse{ + Code: "internal_error", + Message: "failed to list instances", + }, nil + } + return oapi.ListInstances200JSONResponse(insts), nil +} + +// CreateInstance creates and starts a new instance +func (s *ApiService) CreateInstance(ctx context.Context, request oapi.CreateInstanceRequestObject) (oapi.CreateInstanceResponseObject, error) { + log := logger.FromContext(ctx) + + inst, err := s.InstanceManager.CreateInstance(ctx, *request.Body) + if err != nil { + log.Error("failed to create instance", "error", err, "name", request.Body.Name) + return oapi.CreateInstance500JSONResponse{ + Code: "internal_error", + Message: "failed to create instance", + }, nil + } + return oapi.CreateInstance201JSONResponse(*inst), nil +} + +// GetInstance gets instance details +func (s *ApiService) GetInstance(ctx context.Context, request oapi.GetInstanceRequestObject) (oapi.GetInstanceResponseObject, error) { + log := logger.FromContext(ctx) + + inst, err := s.InstanceManager.GetInstance(ctx, request.Id) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.GetInstance404JSONResponse{ + Code: "not_found", + Message: "instance not found", + }, nil + default: + log.Error("failed to get instance", "error", err, "id", request.Id) + return oapi.GetInstance500JSONResponse{ + Code: "internal_error", + Message: "failed to get instance", + }, nil + } + } + return oapi.GetInstance200JSONResponse(*inst), nil +} + +// DeleteInstance stops and deletes an instance +func (s *ApiService) DeleteInstance(ctx context.Context, request oapi.DeleteInstanceRequestObject) (oapi.DeleteInstanceResponseObject, error) { + log := logger.FromContext(ctx) + + err := s.InstanceManager.DeleteInstance(ctx, request.Id) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.DeleteInstance404JSONResponse{ + Code: "not_found", + Message: "instance not found", + }, nil + default: + log.Error("failed to delete instance", "error", err, "id", request.Id) + return oapi.DeleteInstance500JSONResponse{ + Code: "internal_error", + Message: "failed to delete instance", + }, nil + } + } + return oapi.DeleteInstance204Response{}, nil +} + +// StandbyInstance puts an instance in standby (pause, snapshot, delete VMM) +func (s *ApiService) StandbyInstance(ctx context.Context, request oapi.StandbyInstanceRequestObject) (oapi.StandbyInstanceResponseObject, error) { + log := logger.FromContext(ctx) + + inst, err := s.InstanceManager.StandbyInstance(ctx, request.Id) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.StandbyInstance404JSONResponse{ + Code: "not_found", + Message: "instance not found", + }, nil + case errors.Is(err, instances.ErrInvalidState): + return oapi.StandbyInstance409JSONResponse{ + Code: "invalid_state", + Message: "instance is not in a valid state for standby", + }, nil + default: + log.Error("failed to standby instance", "error", err, "id", request.Id) + return oapi.StandbyInstance500JSONResponse{ + Code: "internal_error", + Message: "failed to standby instance", + }, nil + } + } + return oapi.StandbyInstance200JSONResponse(*inst), nil +} + +// RestoreInstance restores an instance from standby +func (s *ApiService) RestoreInstance(ctx context.Context, request oapi.RestoreInstanceRequestObject) (oapi.RestoreInstanceResponseObject, error) { + log := logger.FromContext(ctx) + + inst, err := s.InstanceManager.RestoreInstance(ctx, request.Id) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.RestoreInstance404JSONResponse{ + Code: "not_found", + Message: "instance not found", + }, nil + case errors.Is(err, instances.ErrInvalidState): + return oapi.RestoreInstance409JSONResponse{ + Code: "invalid_state", + Message: "instance is not in standby state", + }, nil + default: + log.Error("failed to restore instance", "error", err, "id", request.Id) + return oapi.RestoreInstance500JSONResponse{ + Code: "internal_error", + Message: "failed to restore instance", + }, nil + } + } + return oapi.RestoreInstance200JSONResponse(*inst), nil +} + +// GetInstanceLogs streams instance logs +func (s *ApiService) GetInstanceLogs(ctx context.Context, request oapi.GetInstanceLogsRequestObject) (oapi.GetInstanceLogsResponseObject, error) { + log := logger.FromContext(ctx) + + follow := false + if request.Params.Follow != nil { + follow = *request.Params.Follow + } + tail := 100 + if request.Params.Tail != nil { + tail = *request.Params.Tail + } + + logs, err := s.InstanceManager.GetInstanceLogs(ctx, request.Id, follow, tail) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.GetInstanceLogs404JSONResponse{ + Code: "not_found", + Message: "instance not found", + }, nil + default: + log.Error("failed to get instance logs", "error", err, "id", request.Id) + return oapi.GetInstanceLogs500JSONResponse{ + Code: "internal_error", + Message: "failed to get instance logs", + }, nil + } + } + + return oapi.GetInstanceLogs200TexteventStreamResponse{ + Body: strings.NewReader(logs), + ContentLength: int64(len(logs)), + }, nil +} + +// AttachVolume attaches a volume to an instance +func (s *ApiService) AttachVolume(ctx context.Context, request oapi.AttachVolumeRequestObject) (oapi.AttachVolumeResponseObject, error) { + log := logger.FromContext(ctx) + + inst, err := s.InstanceManager.AttachVolume(ctx, request.Id, request.VolumeId, *request.Body) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.AttachVolume404JSONResponse{ + Code: "not_found", + Message: "instance or volume not found", + }, nil + default: + log.Error("failed to attach volume", "error", err, "instance_id", request.Id, "volume_id", request.VolumeId) + return oapi.AttachVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to attach volume", + }, nil + } + } + return oapi.AttachVolume200JSONResponse(*inst), nil +} + +// DetachVolume detaches a volume from an instance +func (s *ApiService) DetachVolume(ctx context.Context, request oapi.DetachVolumeRequestObject) (oapi.DetachVolumeResponseObject, error) { + log := logger.FromContext(ctx) + + inst, err := s.InstanceManager.DetachVolume(ctx, request.Id, request.VolumeId) + if err != nil { + switch { + case errors.Is(err, instances.ErrNotFound): + return oapi.DetachVolume404JSONResponse{ + Code: "not_found", + Message: "instance or volume not found", + }, nil + default: + log.Error("failed to detach volume", "error", err, "instance_id", request.Id, "volume_id", request.VolumeId) + return oapi.DetachVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to detach volume", + }, nil + } + } + return oapi.DetachVolume200JSONResponse(*inst), nil +} + diff --git a/cmd/api/api/instances_test.go b/cmd/api/api/instances_test.go new file mode 100644 index 00000000..cdf93fc2 --- /dev/null +++ b/cmd/api/api/instances_test.go @@ -0,0 +1,34 @@ +package api + +import ( + "testing" + + "github.com/onkernel/hypeman/lib/oapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestListInstances_Empty(t *testing.T) { + svc := newTestService(t) + + resp, err := svc.ListInstances(ctx(), oapi.ListInstancesRequestObject{}) + require.NoError(t, err) + + list, ok := resp.(oapi.ListInstances200JSONResponse) + require.True(t, ok, "expected 200 response") + assert.Empty(t, list) +} + +func TestGetInstance_NotFound(t *testing.T) { + svc := newTestService(t) + + resp, err := svc.GetInstance(ctx(), oapi.GetInstanceRequestObject{ + Id: "non-existent", + }) + require.NoError(t, err) + + notFound, ok := resp.(oapi.GetInstance404JSONResponse) + require.True(t, ok, "expected 404 response") + assert.Equal(t, "not_found", notFound.Code) +} + diff --git a/cmd/api/api/swagger.go b/cmd/api/api/swagger.go new file mode 100644 index 00000000..81c50bdf --- /dev/null +++ b/cmd/api/api/swagger.go @@ -0,0 +1,42 @@ +package api + +import "net/http" + +// SwaggerUIHandler serves the Swagger UI for API documentation +func SwaggerUIHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + html := ` + + + + + Hypeman API Documentation + + + + +
+ + + + +` + w.Write([]byte(html)) +} + diff --git a/cmd/api/api/volumes.go b/cmd/api/api/volumes.go new file mode 100644 index 00000000..81ba2d4f --- /dev/null +++ b/cmd/api/api/volumes.go @@ -0,0 +1,92 @@ +package api + +import ( + "context" + "errors" + + "github.com/onkernel/hypeman/lib/logger" + "github.com/onkernel/hypeman/lib/oapi" + "github.com/onkernel/hypeman/lib/volumes" +) + +// ListVolumes lists all volumes +func (s *ApiService) ListVolumes(ctx context.Context, request oapi.ListVolumesRequestObject) (oapi.ListVolumesResponseObject, error) { + log := logger.FromContext(ctx) + + vols, err := s.VolumeManager.ListVolumes(ctx) + if err != nil { + log.Error("failed to list volumes", "error", err) + return oapi.ListVolumes500JSONResponse{ + Code: "internal_error", + Message: "failed to list volumes", + }, nil + } + return oapi.ListVolumes200JSONResponse(vols), nil +} + +// CreateVolume creates a new volume +func (s *ApiService) CreateVolume(ctx context.Context, request oapi.CreateVolumeRequestObject) (oapi.CreateVolumeResponseObject, error) { + log := logger.FromContext(ctx) + + vol, err := s.VolumeManager.CreateVolume(ctx, *request.Body) + if err != nil { + log.Error("failed to create volume", "error", err, "name", request.Body.Name) + return oapi.CreateVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to create volume", + }, nil + } + return oapi.CreateVolume201JSONResponse(*vol), nil +} + +// GetVolume gets volume details +func (s *ApiService) GetVolume(ctx context.Context, request oapi.GetVolumeRequestObject) (oapi.GetVolumeResponseObject, error) { + log := logger.FromContext(ctx) + + vol, err := s.VolumeManager.GetVolume(ctx, request.Id) + if err != nil { + switch { + case errors.Is(err, volumes.ErrNotFound): + return oapi.GetVolume404JSONResponse{ + Code: "not_found", + Message: "volume not found", + }, nil + default: + log.Error("failed to get volume", "error", err, "id", request.Id) + return oapi.GetVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to get volume", + }, nil + } + } + return oapi.GetVolume200JSONResponse(*vol), nil +} + +// DeleteVolume deletes a volume +func (s *ApiService) DeleteVolume(ctx context.Context, request oapi.DeleteVolumeRequestObject) (oapi.DeleteVolumeResponseObject, error) { + log := logger.FromContext(ctx) + + err := s.VolumeManager.DeleteVolume(ctx, request.Id) + if err != nil { + switch { + case errors.Is(err, volumes.ErrNotFound): + return oapi.DeleteVolume404JSONResponse{ + Code: "not_found", + Message: "volume not found", + }, nil + case errors.Is(err, volumes.ErrInUse): + return oapi.DeleteVolume409JSONResponse{ + Code: "conflict", + Message: "volume is in use by an instance", + }, nil + default: + log.Error("failed to delete volume", "error", err, "id", request.Id) + return oapi.DeleteVolume500JSONResponse{ + Code: "internal_error", + Message: "failed to delete volume", + }, nil + } + } + return oapi.DeleteVolume204Response{}, nil +} + diff --git a/cmd/api/api/volumes_test.go b/cmd/api/api/volumes_test.go new file mode 100644 index 00000000..611ab494 --- /dev/null +++ b/cmd/api/api/volumes_test.go @@ -0,0 +1,34 @@ +package api + +import ( + "testing" + + "github.com/onkernel/hypeman/lib/oapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestListVolumes_Empty(t *testing.T) { + svc := newTestService(t) + + resp, err := svc.ListVolumes(ctx(), oapi.ListVolumesRequestObject{}) + require.NoError(t, err) + + list, ok := resp.(oapi.ListVolumes200JSONResponse) + require.True(t, ok, "expected 200 response") + assert.Empty(t, list) +} + +func TestGetVolume_NotFound(t *testing.T) { + svc := newTestService(t) + + resp, err := svc.GetVolume(ctx(), oapi.GetVolumeRequestObject{ + Id: "non-existent", + }) + require.NoError(t, err) + + notFound, ok := resp.(oapi.GetVolume404JSONResponse) + require.True(t, ok, "expected 404 response") + assert.Equal(t, "not_found", notFound.Code) +} + diff --git a/cmd/api/config/config.go b/cmd/api/config/config.go new file mode 100644 index 00000000..9ed6451b --- /dev/null +++ b/cmd/api/config/config.go @@ -0,0 +1,46 @@ +package config + +import ( + "os" + + "github.com/joho/godotenv" +) + +type Config struct { + Port string + DataDir string + BridgeName string + SubnetCIDR string + SubnetGateway string + ContainerdSocket string + JwtSecret string + DNSServer string +} + +// Load loads configuration from environment variables +// Automatically loads .env file if present +func Load() *Config { + // Try to load .env file (fail silently if not present) + _ = godotenv.Load() + + cfg := &Config{ + Port: getEnv("PORT", "8080"), + DataDir: getEnv("DATA_DIR", "/var/lib/hypeman"), + BridgeName: getEnv("BRIDGE_NAME", "vmbr0"), + SubnetCIDR: getEnv("SUBNET_CIDR", "192.168.100.0/24"), + SubnetGateway: getEnv("SUBNET_GATEWAY", "192.168.100.1"), + ContainerdSocket: getEnv("CONTAINERD_SOCKET", "/run/containerd/containerd.sock"), + JwtSecret: getEnv("JWT_SECRET", ""), + DNSServer: getEnv("DNS_SERVER", "1.1.1.1"), + } + + return cfg +} + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + diff --git a/cmd/api/main.go b/cmd/api/main.go new file mode 100644 index 00000000..a4418641 --- /dev/null +++ b/cmd/api/main.go @@ -0,0 +1,150 @@ +package main + +import ( + "context" + _ "embed" + "errors" + "fmt" + "log/slog" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/ghodss/yaml" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + nethttpmiddleware "github.com/oapi-codegen/nethttp-middleware" + "github.com/onkernel/hypeman" + "github.com/onkernel/hypeman/cmd/api/api" + mw "github.com/onkernel/hypeman/lib/middleware" + "github.com/onkernel/hypeman/lib/oapi" + "golang.org/x/sync/errgroup" +) + +func main() { + if err := run(); err != nil { + slog.Error("application terminated", "error", err) + os.Exit(1) + } +} + +func run() error { + // Initialize app with wire + app, cleanup, err := initializeApp() + if err != nil { + return fmt.Errorf("initialize application: %w", err) + } + defer cleanup() + + ctx, stop := signal.NotifyContext(app.Ctx, os.Interrupt, syscall.SIGTERM) + defer stop() + + logger := app.Logger + + // Validate JWT secret is configured + if app.Config.JwtSecret == "" { + logger.Warn("JWT_SECRET not configured - API authentication will fail") + } + + // Create router + r := chi.NewRouter() + + // Load OpenAPI spec for request validation + spec, err := oapi.GetSwagger() + if err != nil { + return fmt.Errorf("failed to load OpenAPI spec: %w", err) + } + + // Clear servers to avoid host validation issues + // See: https://github.com/oapi-codegen/nethttp-middleware#usage + spec.Servers = nil + + // Authenticated API endpoints + r.Group(func(r chi.Router) { + // Common middleware + r.Use(middleware.RequestID) + r.Use(middleware.RealIP) + r.Use(middleware.Logger) + r.Use(middleware.Recoverer) + r.Use(middleware.Timeout(60 * time.Second)) + + // OpenAPI request validation with authentication + validatorOptions := &nethttpmiddleware.Options{ + Options: openapi3filter.Options{ + AuthenticationFunc: mw.OapiAuthenticationFunc(app.Config.JwtSecret), + }, + ErrorHandler: mw.OapiErrorHandler, + } + r.Use(nethttpmiddleware.OapiRequestValidatorWithOptions(spec, validatorOptions)) + + // Setup strict handler + strictHandler := oapi.NewStrictHandler(app.ApiService, nil) + + // Mount API routes (authentication now handled by validation middleware) + oapi.HandlerWithOptions(strictHandler, oapi.ChiServerOptions{ + BaseRouter: r, + Middlewares: []oapi.MiddlewareFunc{}, + }) + }) + + // Unauthenticated endpoints (outside group) + r.Get("/spec.yaml", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/vnd.oai.openapi") + w.Write(hypeman.OpenAPIYAML) + }) + + r.Get("/spec.json", func(w http.ResponseWriter, r *http.Request) { + jsonData, err := yaml.YAMLToJSON(hypeman.OpenAPIYAML) + if err != nil { + http.Error(w, "Failed to convert YAML to JSON", http.StatusInternalServerError) + logger.ErrorContext(r.Context(), "Failed to convert YAML to JSON", "error", err) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(jsonData) + }) + + r.Get("/swagger", api.SwaggerUIHandler) + + // Create HTTP server + srv := &http.Server{ + Addr: fmt.Sprintf(":%s", app.Config.Port), + Handler: r, + } + + // Error group for coordinated shutdown + grp, gctx := errgroup.WithContext(ctx) + + // Run the server + grp.Go(func() error { + logger.Info("starting hypeman API", "port", app.Config.Port) + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + logger.Error("http server error", "error", err) + return err + } + return nil + }) + + // Shutdown handler + grp.Go(func() error { + <-gctx.Done() + logger.Info("shutdown signal received") + + shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := srv.Shutdown(shutdownCtx); err != nil { + logger.Error("failed to shutdown http server", "error", err) + return err + } + + logger.Info("http server shutdown complete") + return nil + }) + + return grp.Wait() +} + diff --git a/cmd/api/main_test.go b/cmd/api/main_test.go new file mode 100644 index 00000000..8812fff0 --- /dev/null +++ b/cmd/api/main_test.go @@ -0,0 +1,94 @@ +package main + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/go-chi/chi/v5" + "github.com/golang-jwt/jwt/v5" + nethttpmiddleware "github.com/oapi-codegen/nethttp-middleware" + mw "github.com/onkernel/hypeman/lib/middleware" + "github.com/onkernel/hypeman/lib/oapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testJWTSecret = "test-secret-key" + +func generateValidJWT(userID string) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "sub": userID, + "exp": time.Now().Add(time.Hour).Unix(), + }) + return token.SignedString([]byte(testJWTSecret)) +} + +func setupTestRouter(t *testing.T) http.Handler { + spec, err := oapi.GetSwagger() + require.NoError(t, err) + spec.Servers = nil + + r := chi.NewRouter() + r.Use(nethttpmiddleware.OapiRequestValidatorWithOptions(spec, &nethttpmiddleware.Options{ + Options: openapi3filter.Options{ + AuthenticationFunc: mw.OapiAuthenticationFunc(testJWTSecret), + }, + ErrorHandler: mw.OapiErrorHandler, + })) + + // Simple handler for testing + r.Post("/images", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"id":"test"}`)) + }) + + return r +} + +func TestMiddleware_InvalidPayload(t *testing.T) { + router := setupTestRouter(t) + token, err := generateValidJWT("user-123") + require.NoError(t, err) + + // Missing required "name" field + req := httptest.NewRequest(http.MethodPost, "/images", bytes.NewBufferString(`{}`)) + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusBadRequest, w.Code) +} + +func TestMiddleware_InvalidJWT(t *testing.T) { + router := setupTestRouter(t) + + req := httptest.NewRequest(http.MethodPost, "/images", bytes.NewBufferString(`{"name":"test"}`)) + req.Header.Set("Authorization", "Bearer invalid-token") + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusUnauthorized, w.Code) +} + +func TestMiddleware_ValidJWT(t *testing.T) { + router := setupTestRouter(t) + token, err := generateValidJWT("user-123") + require.NoError(t, err) + + req := httptest.NewRequest(http.MethodPost, "/images", bytes.NewBufferString(`{"name":"docker.io/library/nginx:latest"}`)) + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + assert.Equal(t, http.StatusCreated, w.Code) +} diff --git a/cmd/api/wire.go b/cmd/api/wire.go new file mode 100644 index 00000000..f9678ba6 --- /dev/null +++ b/cmd/api/wire.go @@ -0,0 +1,42 @@ +// +build wireinject + +package main + +import ( + "context" + "log/slog" + + "github.com/google/wire" + "github.com/onkernel/hypeman/cmd/api/api" + "github.com/onkernel/hypeman/cmd/api/config" + "github.com/onkernel/hypeman/lib/images" + "github.com/onkernel/hypeman/lib/instances" + "github.com/onkernel/hypeman/lib/providers" + "github.com/onkernel/hypeman/lib/volumes" +) + +// application struct to hold initialized components +type application struct { + Ctx context.Context + Logger *slog.Logger + Config *config.Config + ImageManager images.Manager + InstanceManager instances.Manager + VolumeManager volumes.Manager + ApiService *api.ApiService +} + +// initializeApp is the injector function +func initializeApp() (*application, func(), error) { + panic(wire.Build( + providers.ProvideLogger, + providers.ProvideContext, + providers.ProvideConfig, + providers.ProvideImageManager, + providers.ProvideInstanceManager, + providers.ProvideVolumeManager, + api.New, + wire.Struct(new(application), "*"), + )) +} + diff --git a/cmd/api/wire_gen.go b/cmd/api/wire_gen.go new file mode 100644 index 00000000..1fea4142 --- /dev/null +++ b/cmd/api/wire_gen.go @@ -0,0 +1,59 @@ +// Code generated by Wire. DO NOT EDIT. + +//go:generate go run -mod=mod github.com/google/wire/cmd/wire +//go:build !wireinject +// +build !wireinject + +package main + +import ( + "context" + "github.com/onkernel/hypeman/cmd/api/api" + "github.com/onkernel/hypeman/cmd/api/config" + "github.com/onkernel/hypeman/lib/images" + "github.com/onkernel/hypeman/lib/instances" + "github.com/onkernel/hypeman/lib/providers" + "github.com/onkernel/hypeman/lib/volumes" + "log/slog" +) + +import ( + _ "embed" +) + +// Injectors from wire.go: + +// initializeApp is the injector function +func initializeApp() (*application, func(), error) { + logger := providers.ProvideLogger() + context := providers.ProvideContext(logger) + config := providers.ProvideConfig() + manager := providers.ProvideImageManager(config) + instancesManager := providers.ProvideInstanceManager(config) + volumesManager := providers.ProvideVolumeManager(config) + apiService := api.New(config, manager, instancesManager, volumesManager) + mainApplication := &application{ + Ctx: context, + Logger: logger, + Config: config, + ImageManager: manager, + InstanceManager: instancesManager, + VolumeManager: volumesManager, + ApiService: apiService, + } + return mainApplication, func() { + }, nil +} + +// wire.go: + +// application struct to hold initialized components +type application struct { + Ctx context.Context + Logger *slog.Logger + Config *config.Config + ImageManager images.Manager + InstanceManager instances.Manager + VolumeManager volumes.Manager + ApiService *api.ApiService +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..ab83bfc6 --- /dev/null +++ b/go.mod @@ -0,0 +1,35 @@ +module github.com/onkernel/hypeman + +go 1.25.4 + +require ( + github.com/getkin/kin-openapi v0.133.0 + github.com/ghodss/yaml v1.0.0 + github.com/go-chi/chi/v5 v5.2.3 + github.com/golang-jwt/jwt/v5 v5.3.0 + github.com/google/wire v0.7.0 + github.com/joho/godotenv v1.5.1 + github.com/oapi-codegen/nethttp-middleware v1.1.2 + github.com/oapi-codegen/runtime v1.1.2 + github.com/stretchr/testify v1.11.1 + golang.org/x/sync v0.17.0 +) + +require ( + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect + github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect + github.com/perimeterx/marshmallow v1.1.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..2ced6493 --- /dev/null +++ b/go.sum @@ -0,0 +1,72 @@ +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4= +github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/oapi-codegen/nethttp-middleware v1.1.2 h1:TQwEU3WM6ifc7ObBEtiJgbRPaCe513tvJpiMJjypVPA= +github.com/oapi-codegen/nethttp-middleware v1.1.2/go.mod h1:5qzjxMSiI8HjLljiOEjvs4RdrWyMPKnExeFS2kr8om4= +github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= +github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= +github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c= +github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/images/errors.go b/lib/images/errors.go new file mode 100644 index 00000000..29baf9e9 --- /dev/null +++ b/lib/images/errors.go @@ -0,0 +1,9 @@ +package images + +import "errors" + +var ( + ErrNotFound = errors.New("image not found") + ErrAlreadyExists = errors.New("image already exists") +) + diff --git a/lib/images/manager.go b/lib/images/manager.go new file mode 100644 index 00000000..0f7f2104 --- /dev/null +++ b/lib/images/manager.go @@ -0,0 +1,58 @@ +package images + +import ( + "context" + "fmt" + + "github.com/onkernel/hypeman/lib/oapi" +) + +// Manager handles image lifecycle operations +type Manager interface { + ListImages(ctx context.Context) ([]oapi.Image, error) + CreateImage(ctx context.Context, req oapi.CreateImageRequest) (*oapi.Image, error) + GetImage(ctx context.Context, id string) (*oapi.Image, error) + DeleteImage(ctx context.Context, id string) error +} + +type manager struct { + dataDir string +} + +// NewManager creates a new image manager +func NewManager(dataDir string) Manager { + return &manager{ + dataDir: dataDir, + } +} + +func (m *manager) ListImages(ctx context.Context) ([]oapi.Image, error) { + // TODO: implement + return []oapi.Image{}, nil +} + +func (m *manager) CreateImage(ctx context.Context, req oapi.CreateImageRequest) (*oapi.Image, error) { + // TODO: implement actual logic + // Example: check if already exists + exists := false + if exists { + return nil, ErrAlreadyExists + } + return nil, fmt.Errorf("image creation not yet implemented") +} + +func (m *manager) GetImage(ctx context.Context, id string) (*oapi.Image, error) { + // TODO: implement actual logic + // For now, always return not found since we have no images + return nil, ErrNotFound +} + +func (m *manager) DeleteImage(ctx context.Context, id string) error { + // TODO: implement actual logic + exists := false + if !exists { + return ErrNotFound + } + return fmt.Errorf("delete image not yet implemented") +} + diff --git a/lib/instances/errors.go b/lib/instances/errors.go new file mode 100644 index 00000000..ead9354f --- /dev/null +++ b/lib/instances/errors.go @@ -0,0 +1,9 @@ +package instances + +import "errors" + +var ( + ErrNotFound = errors.New("instance not found") + ErrInvalidState = errors.New("invalid instance state for this operation") +) + diff --git a/lib/instances/manager.go b/lib/instances/manager.go new file mode 100644 index 00000000..58978307 --- /dev/null +++ b/lib/instances/manager.go @@ -0,0 +1,104 @@ +package instances + +import ( + "context" + "fmt" + + "github.com/onkernel/hypeman/lib/oapi" +) + +// Manager handles instance lifecycle operations +type Manager interface { + ListInstances(ctx context.Context) ([]oapi.Instance, error) + CreateInstance(ctx context.Context, req oapi.CreateInstanceRequest) (*oapi.Instance, error) + GetInstance(ctx context.Context, id string) (*oapi.Instance, error) + DeleteInstance(ctx context.Context, id string) error + StandbyInstance(ctx context.Context, id string) (*oapi.Instance, error) + RestoreInstance(ctx context.Context, id string) (*oapi.Instance, error) + GetInstanceLogs(ctx context.Context, id string, follow bool, tail int) (string, error) + AttachVolume(ctx context.Context, id string, volumeId string, req oapi.AttachVolumeRequest) (*oapi.Instance, error) + DetachVolume(ctx context.Context, id string, volumeId string) (*oapi.Instance, error) +} + +type manager struct { + dataDir string +} + +// NewManager creates a new instance manager +func NewManager(dataDir string) Manager { + return &manager{ + dataDir: dataDir, + } +} + +func (m *manager) ListInstances(ctx context.Context) ([]oapi.Instance, error) { + // TODO: implement + return []oapi.Instance{}, nil +} + +func (m *manager) CreateInstance(ctx context.Context, req oapi.CreateInstanceRequest) (*oapi.Instance, error) { + // TODO: implement + return nil, fmt.Errorf("instance creation not yet implemented") +} + +func (m *manager) GetInstance(ctx context.Context, id string) (*oapi.Instance, error) { + // TODO: implement actual logic + exists := false + if !exists { + return nil, ErrNotFound + } + return nil, fmt.Errorf("get instance not yet implemented") +} + +func (m *manager) DeleteInstance(ctx context.Context, id string) error { + // TODO: implement actual logic + exists := false + if !exists { + return ErrNotFound + } + return fmt.Errorf("delete instance not yet implemented") +} + +func (m *manager) StandbyInstance(ctx context.Context, id string) (*oapi.Instance, error) { + // TODO: implement actual logic + exists := false + if !exists { + return nil, ErrNotFound + } + // Check if instance is in correct state (e.g., Running) + validState := false + if !validState { + return nil, ErrInvalidState + } + return nil, fmt.Errorf("standby instance not yet implemented") +} + +func (m *manager) RestoreInstance(ctx context.Context, id string) (*oapi.Instance, error) { + // TODO: implement actual logic + exists := false + if !exists { + return nil, ErrNotFound + } + // Check if instance is in Standby state + inStandby := false + if !inStandby { + return nil, ErrInvalidState + } + return nil, fmt.Errorf("restore instance not yet implemented") +} + +func (m *manager) GetInstanceLogs(ctx context.Context, id string, follow bool, tail int) (string, error) { + // TODO: implement + return "", fmt.Errorf("get instance logs not yet implemented") +} + +func (m *manager) AttachVolume(ctx context.Context, id string, volumeId string, req oapi.AttachVolumeRequest) (*oapi.Instance, error) { + // TODO: implement + return nil, fmt.Errorf("attach volume not yet implemented") +} + +func (m *manager) DetachVolume(ctx context.Context, id string, volumeId string) (*oapi.Instance, error) { + // TODO: implement + return nil, fmt.Errorf("detach volume not yet implemented") +} + diff --git a/lib/logger/logger.go b/lib/logger/logger.go new file mode 100644 index 00000000..b4ac1528 --- /dev/null +++ b/lib/logger/logger.go @@ -0,0 +1,24 @@ +package logger + +import ( + "context" + "log/slog" +) + +type contextKey string + +const loggerKey contextKey = "logger" + +// AddToContext adds a logger to the context +func AddToContext(ctx context.Context, logger *slog.Logger) context.Context { + return context.WithValue(ctx, loggerKey, logger) +} + +// FromContext retrieves the logger from context, or returns default +func FromContext(ctx context.Context) *slog.Logger { + if logger, ok := ctx.Value(loggerKey).(*slog.Logger); ok { + return logger + } + return slog.Default() +} + diff --git a/lib/middleware/oapi_auth.go b/lib/middleware/oapi_auth.go new file mode 100644 index 00000000..e0599cbd --- /dev/null +++ b/lib/middleware/oapi_auth.go @@ -0,0 +1,118 @@ +package middleware + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/getkin/kin-openapi/openapi3filter" + "github.com/golang-jwt/jwt/v5" + "github.com/onkernel/hypeman/lib/logger" +) + +type contextKey string + +const userIDKey contextKey = "user_id" + +// OapiAuthenticationFunc creates an AuthenticationFunc compatible with nethttp-middleware +// that validates JWT bearer tokens for endpoints with security requirements. +func OapiAuthenticationFunc(jwtSecret string) openapi3filter.AuthenticationFunc { + return func(ctx context.Context, input *openapi3filter.AuthenticationInput) error { + log := logger.FromContext(ctx) + + // If no security requirements, allow the request + if input.SecurityScheme == nil { + return nil + } + + // Only handle bearer auth + if input.SecurityScheme.Type != "http" || input.SecurityScheme.Scheme != "bearer" { + return fmt.Errorf("unsupported security scheme: %s", input.SecurityScheme.Type) + } + + // Extract token from Authorization header + authHeader := input.RequestValidationInput.Request.Header.Get("Authorization") + if authHeader == "" { + log.DebugContext(ctx, "missing authorization header") + return fmt.Errorf("authorization header required") + } + + // Extract bearer token + token, err := extractBearerToken(authHeader) + if err != nil { + log.DebugContext(ctx, "invalid authorization header", "error", err) + return fmt.Errorf("invalid authorization header format") + } + + // Parse and validate JWT + claims := jwt.MapClaims{} + parsedToken, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { + // Validate signing method + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return []byte(jwtSecret), nil + }) + + if err != nil { + log.DebugContext(ctx, "failed to parse JWT", "error", err) + return fmt.Errorf("invalid token") + } + + if !parsedToken.Valid { + log.DebugContext(ctx, "invalid JWT token") + return fmt.Errorf("invalid token") + } + + // Extract user ID from claims and add to context + var userID string + if sub, ok := claims["sub"].(string); ok { + userID = sub + } + + // Update the context with user ID + newCtx := context.WithValue(ctx, userIDKey, userID) + + // Update the request with the new context + *input.RequestValidationInput.Request = *input.RequestValidationInput.Request.WithContext(newCtx) + + return nil + } +} + +// OapiErrorHandler creates a custom error handler for nethttp-middleware +// that returns consistent error responses. +func OapiErrorHandler(w http.ResponseWriter, message string, statusCode int) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(statusCode) + + // Return a simple JSON error response matching our Error schema + fmt.Fprintf(w, `{"code":"%s","message":"%s"}`, + http.StatusText(statusCode), + message) +} + +// extractBearerToken extracts the token from "Bearer " format +func extractBearerToken(authHeader string) (string, error) { + parts := strings.SplitN(authHeader, " ", 2) + if len(parts) != 2 { + return "", fmt.Errorf("invalid authorization header format") + } + + scheme := strings.ToLower(parts[0]) + if scheme != "bearer" { + return "", fmt.Errorf("unsupported authorization scheme: %s", scheme) + } + + return parts[1], nil +} + +// GetUserIDFromContext extracts the user ID from context +func GetUserIDFromContext(ctx context.Context) string { + if userID, ok := ctx.Value(userIDKey).(string); ok { + return userID + } + return "" +} + diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go new file mode 100644 index 00000000..4950de53 --- /dev/null +++ b/lib/oapi/oapi.go @@ -0,0 +1,5089 @@ +// Package oapi provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT. +package oapi + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "path" + "strings" + "time" + + "github.com/getkin/kin-openapi/openapi3" + "github.com/go-chi/chi/v5" + "github.com/oapi-codegen/runtime" + strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp" +) + +const ( + BearerAuthScopes = "bearerAuth.Scopes" +) + +// Defines values for HealthStatus. +const ( + Ok HealthStatus = "ok" +) + +// Defines values for InstanceState. +const ( + Created InstanceState = "Created" + Paused InstanceState = "Paused" + Running InstanceState = "Running" + Shutdown InstanceState = "Shutdown" + Standby InstanceState = "Standby" + Stopped InstanceState = "Stopped" +) + +// Defines values for PortMappingProtocol. +const ( + Tcp PortMappingProtocol = "tcp" + Udp PortMappingProtocol = "udp" +) + +// AttachVolumeRequest defines model for AttachVolumeRequest. +type AttachVolumeRequest struct { + // MountPath Path where volume should be mounted + MountPath string `json:"mount_path"` + + // Readonly Mount as read-only + Readonly *bool `json:"readonly,omitempty"` +} + +// CreateImageRequest defines model for CreateImageRequest. +type CreateImageRequest struct { + // Id Optional custom identifier (auto-generated if not provided) + Id *string `json:"id,omitempty"` + + // Name OCI image reference (e.g., docker.io/library/nginx:latest) + Name string `json:"name"` +} + +// CreateInstanceRequest defines model for CreateInstanceRequest. +type CreateInstanceRequest struct { + // Env Environment variables + Env *map[string]string `json:"env,omitempty"` + + // Id Unique identifier for the instance (provided by caller) + Id string `json:"id"` + + // Image Image identifier + Image string `json:"image"` + + // MemoryMaxMb Maximum memory with hotplug in MB + MemoryMaxMb *int `json:"memory_max_mb,omitempty"` + + // MemoryMb Base memory in MB + MemoryMb *int `json:"memory_mb,omitempty"` + + // Name Human-readable name + Name string `json:"name"` + + // PortMappings Port mappings from host to guest + PortMappings *[]PortMapping `json:"port_mappings,omitempty"` + + // TimeoutSeconds Timeout for scale-to-zero semantics + TimeoutSeconds *int `json:"timeout_seconds,omitempty"` + + // Vcpus Number of virtual CPUs + Vcpus *int `json:"vcpus,omitempty"` + + // Volumes Volumes to attach + Volumes *[]VolumeAttachment `json:"volumes,omitempty"` +} + +// CreateVolumeRequest defines model for CreateVolumeRequest. +type CreateVolumeRequest struct { + // Id Optional custom identifier (auto-generated if not provided) + Id *string `json:"id,omitempty"` + + // Name Volume name + Name string `json:"name"` + + // SizeGb Size in gigabytes + SizeGb int `json:"size_gb"` +} + +// Error defines model for Error. +type Error struct { + // Code Application-specific error code (machine-readable) + Code string `json:"code"` + + // Details Additional error details (for multiple errors) + Details *[]ErrorDetail `json:"details,omitempty"` + InnerError *ErrorDetail `json:"inner_error,omitempty"` + + // Message Human-readable error description for debugging + Message string `json:"message"` +} + +// ErrorDetail defines model for ErrorDetail. +type ErrorDetail struct { + // Code Lower-level error code providing more specific detail + Code *string `json:"code,omitempty"` + + // Message Further detail about the error + Message *string `json:"message,omitempty"` +} + +// Health defines model for Health. +type Health struct { + Status HealthStatus `json:"status"` +} + +// HealthStatus defines model for Health.Status. +type HealthStatus string + +// Image defines model for Image. +type Image struct { + // Cmd CMD from container metadata + Cmd *[]string `json:"cmd"` + + // CreatedAt Creation timestamp (RFC3339) + CreatedAt time.Time `json:"created_at"` + + // Entrypoint Entrypoint from container metadata + Entrypoint *[]string `json:"entrypoint"` + + // Env Environment variables from container metadata + Env *map[string]string `json:"env,omitempty"` + + // Id Unique identifier + Id string `json:"id"` + + // Name OCI image reference + Name string `json:"name"` + + // SizeBytes Disk size in bytes + SizeBytes *int64 `json:"size_bytes,omitempty"` + + // Version Image tag or digest + Version *string `json:"version"` + + // WorkingDir Working directory from container metadata + WorkingDir *string `json:"working_dir"` +} + +// Instance defines model for Instance. +type Instance struct { + // CreatedAt Creation timestamp (RFC3339) + CreatedAt time.Time `json:"created_at"` + + // Env Environment variables + Env *map[string]string `json:"env,omitempty"` + + // Fqdn Fully qualified domain name + Fqdn *string `json:"fqdn"` + + // HasSnapshot Whether a snapshot exists for this instance + HasSnapshot *bool `json:"has_snapshot,omitempty"` + + // Id Unique identifier + Id string `json:"id"` + + // Image Image identifier + Image string `json:"image"` + + // MemoryMaxMb Configured maximum memory in MB + MemoryMaxMb *int `json:"memory_max_mb,omitempty"` + + // MemoryMb Configured base memory in MB + MemoryMb *int `json:"memory_mb,omitempty"` + + // Name Human-readable name + Name string `json:"name"` + + // PortMappings Port mappings + PortMappings *[]PortMapping `json:"port_mappings,omitempty"` + + // PrivateIp Private IP address + PrivateIp *string `json:"private_ip"` + + // StartedAt Start timestamp (RFC3339) + StartedAt *time.Time `json:"started_at"` + + // State Instance state: + // - Created: VMM created but not started (Cloud Hypervisor native) + // - Running: VM is actively running (Cloud Hypervisor native) + // - Paused: VM is paused (Cloud Hypervisor native) + // - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native) + // - Stopped: No VMM running, no snapshot exists + // - Standby: No VMM running, snapshot exists (can be restored) + State InstanceState `json:"state"` + + // StoppedAt Stop timestamp (RFC3339) + StoppedAt *time.Time `json:"stopped_at"` + + // TimeoutSeconds Timeout configuration + TimeoutSeconds *int `json:"timeout_seconds,omitempty"` + + // Vcpus Number of virtual CPUs + Vcpus *int `json:"vcpus,omitempty"` + + // Volumes Attached volumes + Volumes *[]VolumeAttachment `json:"volumes,omitempty"` +} + +// InstanceState Instance state: +// - Created: VMM created but not started (Cloud Hypervisor native) +// - Running: VM is actively running (Cloud Hypervisor native) +// - Paused: VM is paused (Cloud Hypervisor native) +// - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native) +// - Stopped: No VMM running, no snapshot exists +// - Standby: No VMM running, snapshot exists (can be restored) +type InstanceState string + +// PortMapping defines model for PortMapping. +type PortMapping struct { + // GuestPort Port in the guest VM + GuestPort int `json:"guest_port"` + + // HostPort Port on the host + HostPort int `json:"host_port"` + Protocol *PortMappingProtocol `json:"protocol,omitempty"` +} + +// PortMappingProtocol defines model for PortMapping.Protocol. +type PortMappingProtocol string + +// Volume defines model for Volume. +type Volume struct { + // AttachedTo Instance ID if attached + AttachedTo *string `json:"attached_to"` + + // CreatedAt Creation timestamp (RFC3339) + CreatedAt time.Time `json:"created_at"` + + // Id Unique identifier + Id string `json:"id"` + + // MountPath Mount path if attached + MountPath *string `json:"mount_path"` + + // Name Volume name + Name string `json:"name"` + + // SizeGb Size in gigabytes + SizeGb int `json:"size_gb"` +} + +// VolumeAttachment defines model for VolumeAttachment. +type VolumeAttachment struct { + // MountPath Path where volume is mounted in the guest + MountPath string `json:"mount_path"` + + // Readonly Whether volume is mounted read-only + Readonly *bool `json:"readonly,omitempty"` + + // VolumeId Volume identifier + VolumeId string `json:"volume_id"` +} + +// GetInstanceLogsParams defines parameters for GetInstanceLogs. +type GetInstanceLogsParams struct { + // Follow Follow logs (stream with SSE) + Follow *bool `form:"follow,omitempty" json:"follow,omitempty"` + + // Tail Number of lines to return from end + Tail *int `form:"tail,omitempty" json:"tail,omitempty"` +} + +// CreateImageJSONRequestBody defines body for CreateImage for application/json ContentType. +type CreateImageJSONRequestBody = CreateImageRequest + +// CreateInstanceJSONRequestBody defines body for CreateInstance for application/json ContentType. +type CreateInstanceJSONRequestBody = CreateInstanceRequest + +// AttachVolumeJSONRequestBody defines body for AttachVolume for application/json ContentType. +type AttachVolumeJSONRequestBody = AttachVolumeRequest + +// CreateVolumeJSONRequestBody defines body for CreateVolume for application/json ContentType. +type CreateVolumeJSONRequestBody = CreateVolumeRequest + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetHealth request + GetHealth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListImages request + ListImages(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateImageWithBody request with any body + CreateImageWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateImage(ctx context.Context, body CreateImageJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteImage request + DeleteImage(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetImage request + GetImage(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListInstances request + ListInstances(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateInstanceWithBody request with any body + CreateInstanceWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateInstance(ctx context.Context, body CreateInstanceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteInstance request + DeleteInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetInstance request + GetInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetInstanceLogs request + GetInstanceLogs(ctx context.Context, id string, params *GetInstanceLogsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // RestoreInstance request + RestoreInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // StandbyInstance request + StandbyInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DetachVolume request + DetachVolume(ctx context.Context, id string, volumeId string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // AttachVolumeWithBody request with any body + AttachVolumeWithBody(ctx context.Context, id string, volumeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + AttachVolume(ctx context.Context, id string, volumeId string, body AttachVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ListVolumes request + ListVolumes(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateVolumeWithBody request with any body + CreateVolumeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateVolume(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteVolume request + DeleteVolume(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetVolume request + GetVolume(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetHealth(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetHealthRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListImages(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListImagesRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateImageWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateImageRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateImage(ctx context.Context, body CreateImageJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateImageRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteImage(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteImageRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetImage(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetImageRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListInstances(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListInstancesRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateInstanceWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateInstanceRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateInstance(ctx context.Context, body CreateInstanceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateInstanceRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteInstanceRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetInstanceRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetInstanceLogs(ctx context.Context, id string, params *GetInstanceLogsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetInstanceLogsRequest(c.Server, id, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) RestoreInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewRestoreInstanceRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) StandbyInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewStandbyInstanceRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DetachVolume(ctx context.Context, id string, volumeId string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDetachVolumeRequest(c.Server, id, volumeId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AttachVolumeWithBody(ctx context.Context, id string, volumeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAttachVolumeRequestWithBody(c.Server, id, volumeId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) AttachVolume(ctx context.Context, id string, volumeId string, body AttachVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewAttachVolumeRequest(c.Server, id, volumeId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ListVolumes(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListVolumesRequest(c.Server) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateVolumeWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateVolumeRequestWithBody(c.Server, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateVolume(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateVolumeRequest(c.Server, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteVolume(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteVolumeRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetVolume(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetVolumeRequest(c.Server, id) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetHealthRequest generates requests for GetHealth +func NewGetHealthRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/health") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListImagesRequest generates requests for ListImages +func NewListImagesRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/images") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateImageRequest calls the generic CreateImage builder with application/json body +func NewCreateImageRequest(server string, body CreateImageJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateImageRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateImageRequestWithBody generates requests for CreateImage with any type of body +func NewCreateImageRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/images") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteImageRequest generates requests for DeleteImage +func NewDeleteImageRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/images/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetImageRequest generates requests for GetImage +func NewGetImageRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/images/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewListInstancesRequest generates requests for ListInstances +func NewListInstancesRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateInstanceRequest calls the generic CreateInstance builder with application/json body +func NewCreateInstanceRequest(server string, body CreateInstanceJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateInstanceRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateInstanceRequestWithBody generates requests for CreateInstance with any type of body +func NewCreateInstanceRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteInstanceRequest generates requests for DeleteInstance +func NewDeleteInstanceRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetInstanceRequest generates requests for GetInstance +func NewGetInstanceRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetInstanceLogsRequest generates requests for GetInstanceLogs +func NewGetInstanceLogsRequest(server string, id string, params *GetInstanceLogsParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s/logs", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Follow != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "follow", runtime.ParamLocationQuery, *params.Follow); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Tail != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "tail", runtime.ParamLocationQuery, *params.Tail); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewRestoreInstanceRequest generates requests for RestoreInstance +func NewRestoreInstanceRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s/restore", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewStandbyInstanceRequest generates requests for StandbyInstance +func NewStandbyInstanceRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s/standby", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewDetachVolumeRequest generates requests for DetachVolume +func NewDetachVolumeRequest(server string, id string, volumeId string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "volumeId", runtime.ParamLocationPath, volumeId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s/volumes/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewAttachVolumeRequest calls the generic AttachVolume builder with application/json body +func NewAttachVolumeRequest(server string, id string, volumeId string, body AttachVolumeJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewAttachVolumeRequestWithBody(server, id, volumeId, "application/json", bodyReader) +} + +// NewAttachVolumeRequestWithBody generates requests for AttachVolume with any type of body +func NewAttachVolumeRequestWithBody(server string, id string, volumeId string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "volumeId", runtime.ParamLocationPath, volumeId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s/volumes/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewListVolumesRequest generates requests for ListVolumes +func NewListVolumesRequest(server string) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/volumes") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateVolumeRequest calls the generic CreateVolume builder with application/json body +func NewCreateVolumeRequest(server string, body CreateVolumeJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateVolumeRequestWithBody(server, "application/json", bodyReader) +} + +// NewCreateVolumeRequestWithBody generates requests for CreateVolume with any type of body +func NewCreateVolumeRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/volumes") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteVolumeRequest generates requests for DeleteVolume +func NewDeleteVolumeRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/volumes/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetVolumeRequest generates requests for GetVolume +func NewGetVolumeRequest(server string, id string) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/volumes/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetHealthWithResponse request + GetHealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetHealthResponse, error) + + // ListImagesWithResponse request + ListImagesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListImagesResponse, error) + + // CreateImageWithBodyWithResponse request with any body + CreateImageWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateImageResponse, error) + + CreateImageWithResponse(ctx context.Context, body CreateImageJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateImageResponse, error) + + // DeleteImageWithResponse request + DeleteImageWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteImageResponse, error) + + // GetImageWithResponse request + GetImageWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetImageResponse, error) + + // ListInstancesWithResponse request + ListInstancesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListInstancesResponse, error) + + // CreateInstanceWithBodyWithResponse request with any body + CreateInstanceWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateInstanceResponse, error) + + CreateInstanceWithResponse(ctx context.Context, body CreateInstanceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateInstanceResponse, error) + + // DeleteInstanceWithResponse request + DeleteInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteInstanceResponse, error) + + // GetInstanceWithResponse request + GetInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetInstanceResponse, error) + + // GetInstanceLogsWithResponse request + GetInstanceLogsWithResponse(ctx context.Context, id string, params *GetInstanceLogsParams, reqEditors ...RequestEditorFn) (*GetInstanceLogsResponse, error) + + // RestoreInstanceWithResponse request + RestoreInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*RestoreInstanceResponse, error) + + // StandbyInstanceWithResponse request + StandbyInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*StandbyInstanceResponse, error) + + // DetachVolumeWithResponse request + DetachVolumeWithResponse(ctx context.Context, id string, volumeId string, reqEditors ...RequestEditorFn) (*DetachVolumeResponse, error) + + // AttachVolumeWithBodyWithResponse request with any body + AttachVolumeWithBodyWithResponse(ctx context.Context, id string, volumeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachVolumeResponse, error) + + AttachVolumeWithResponse(ctx context.Context, id string, volumeId string, body AttachVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachVolumeResponse, error) + + // ListVolumesWithResponse request + ListVolumesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListVolumesResponse, error) + + // CreateVolumeWithBodyWithResponse request with any body + CreateVolumeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVolumeResponse, error) + + CreateVolumeWithResponse(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVolumeResponse, error) + + // DeleteVolumeWithResponse request + DeleteVolumeWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteVolumeResponse, error) + + // GetVolumeWithResponse request + GetVolumeWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetVolumeResponse, error) +} + +type GetHealthResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Health +} + +// Status returns HTTPResponse.Status +func (r GetHealthResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetHealthResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListImagesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Image + JSON401 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r ListImagesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListImagesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateImageResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Image + JSON400 *Error + JSON401 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r CreateImageResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateImageResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteImageResponse struct { + Body []byte + HTTPResponse *http.Response + JSON404 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r DeleteImageResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteImageResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetImageResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Image + JSON404 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r GetImageResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetImageResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListInstancesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Instance + JSON401 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r ListInstancesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListInstancesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateInstanceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Instance + JSON400 *Error + JSON401 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r CreateInstanceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateInstanceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteInstanceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON404 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r DeleteInstanceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteInstanceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetInstanceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Instance + JSON404 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r GetInstanceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetInstanceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetInstanceLogsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON404 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r GetInstanceLogsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetInstanceLogsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type RestoreInstanceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Instance + JSON404 *Error + JSON409 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r RestoreInstanceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r RestoreInstanceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type StandbyInstanceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Instance + JSON404 *Error + JSON409 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r StandbyInstanceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r StandbyInstanceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DetachVolumeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Instance + JSON404 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r DetachVolumeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DetachVolumeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type AttachVolumeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Instance + JSON404 *Error + JSON409 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r AttachVolumeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r AttachVolumeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ListVolumesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *[]Volume + JSON401 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r ListVolumesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ListVolumesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateVolumeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Volume + JSON400 *Error + JSON401 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r CreateVolumeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateVolumeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteVolumeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON404 *Error + JSON409 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r DeleteVolumeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteVolumeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetVolumeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Volume + JSON404 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r GetVolumeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetVolumeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetHealthWithResponse request returning *GetHealthResponse +func (c *ClientWithResponses) GetHealthWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetHealthResponse, error) { + rsp, err := c.GetHealth(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetHealthResponse(rsp) +} + +// ListImagesWithResponse request returning *ListImagesResponse +func (c *ClientWithResponses) ListImagesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListImagesResponse, error) { + rsp, err := c.ListImages(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseListImagesResponse(rsp) +} + +// CreateImageWithBodyWithResponse request with arbitrary body returning *CreateImageResponse +func (c *ClientWithResponses) CreateImageWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateImageResponse, error) { + rsp, err := c.CreateImageWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateImageResponse(rsp) +} + +func (c *ClientWithResponses) CreateImageWithResponse(ctx context.Context, body CreateImageJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateImageResponse, error) { + rsp, err := c.CreateImage(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateImageResponse(rsp) +} + +// DeleteImageWithResponse request returning *DeleteImageResponse +func (c *ClientWithResponses) DeleteImageWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteImageResponse, error) { + rsp, err := c.DeleteImage(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteImageResponse(rsp) +} + +// GetImageWithResponse request returning *GetImageResponse +func (c *ClientWithResponses) GetImageWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetImageResponse, error) { + rsp, err := c.GetImage(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetImageResponse(rsp) +} + +// ListInstancesWithResponse request returning *ListInstancesResponse +func (c *ClientWithResponses) ListInstancesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListInstancesResponse, error) { + rsp, err := c.ListInstances(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseListInstancesResponse(rsp) +} + +// CreateInstanceWithBodyWithResponse request with arbitrary body returning *CreateInstanceResponse +func (c *ClientWithResponses) CreateInstanceWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateInstanceResponse, error) { + rsp, err := c.CreateInstanceWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateInstanceResponse(rsp) +} + +func (c *ClientWithResponses) CreateInstanceWithResponse(ctx context.Context, body CreateInstanceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateInstanceResponse, error) { + rsp, err := c.CreateInstance(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateInstanceResponse(rsp) +} + +// DeleteInstanceWithResponse request returning *DeleteInstanceResponse +func (c *ClientWithResponses) DeleteInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteInstanceResponse, error) { + rsp, err := c.DeleteInstance(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteInstanceResponse(rsp) +} + +// GetInstanceWithResponse request returning *GetInstanceResponse +func (c *ClientWithResponses) GetInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetInstanceResponse, error) { + rsp, err := c.GetInstance(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetInstanceResponse(rsp) +} + +// GetInstanceLogsWithResponse request returning *GetInstanceLogsResponse +func (c *ClientWithResponses) GetInstanceLogsWithResponse(ctx context.Context, id string, params *GetInstanceLogsParams, reqEditors ...RequestEditorFn) (*GetInstanceLogsResponse, error) { + rsp, err := c.GetInstanceLogs(ctx, id, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetInstanceLogsResponse(rsp) +} + +// RestoreInstanceWithResponse request returning *RestoreInstanceResponse +func (c *ClientWithResponses) RestoreInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*RestoreInstanceResponse, error) { + rsp, err := c.RestoreInstance(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseRestoreInstanceResponse(rsp) +} + +// StandbyInstanceWithResponse request returning *StandbyInstanceResponse +func (c *ClientWithResponses) StandbyInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*StandbyInstanceResponse, error) { + rsp, err := c.StandbyInstance(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseStandbyInstanceResponse(rsp) +} + +// DetachVolumeWithResponse request returning *DetachVolumeResponse +func (c *ClientWithResponses) DetachVolumeWithResponse(ctx context.Context, id string, volumeId string, reqEditors ...RequestEditorFn) (*DetachVolumeResponse, error) { + rsp, err := c.DetachVolume(ctx, id, volumeId, reqEditors...) + if err != nil { + return nil, err + } + return ParseDetachVolumeResponse(rsp) +} + +// AttachVolumeWithBodyWithResponse request with arbitrary body returning *AttachVolumeResponse +func (c *ClientWithResponses) AttachVolumeWithBodyWithResponse(ctx context.Context, id string, volumeId string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*AttachVolumeResponse, error) { + rsp, err := c.AttachVolumeWithBody(ctx, id, volumeId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAttachVolumeResponse(rsp) +} + +func (c *ClientWithResponses) AttachVolumeWithResponse(ctx context.Context, id string, volumeId string, body AttachVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*AttachVolumeResponse, error) { + rsp, err := c.AttachVolume(ctx, id, volumeId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseAttachVolumeResponse(rsp) +} + +// ListVolumesWithResponse request returning *ListVolumesResponse +func (c *ClientWithResponses) ListVolumesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListVolumesResponse, error) { + rsp, err := c.ListVolumes(ctx, reqEditors...) + if err != nil { + return nil, err + } + return ParseListVolumesResponse(rsp) +} + +// CreateVolumeWithBodyWithResponse request with arbitrary body returning *CreateVolumeResponse +func (c *ClientWithResponses) CreateVolumeWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateVolumeResponse, error) { + rsp, err := c.CreateVolumeWithBody(ctx, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateVolumeResponse(rsp) +} + +func (c *ClientWithResponses) CreateVolumeWithResponse(ctx context.Context, body CreateVolumeJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateVolumeResponse, error) { + rsp, err := c.CreateVolume(ctx, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateVolumeResponse(rsp) +} + +// DeleteVolumeWithResponse request returning *DeleteVolumeResponse +func (c *ClientWithResponses) DeleteVolumeWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*DeleteVolumeResponse, error) { + rsp, err := c.DeleteVolume(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteVolumeResponse(rsp) +} + +// GetVolumeWithResponse request returning *GetVolumeResponse +func (c *ClientWithResponses) GetVolumeWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetVolumeResponse, error) { + rsp, err := c.GetVolume(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetVolumeResponse(rsp) +} + +// ParseGetHealthResponse parses an HTTP response from a GetHealthWithResponse call +func ParseGetHealthResponse(rsp *http.Response) (*GetHealthResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetHealthResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Health + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseListImagesResponse parses an HTTP response from a ListImagesWithResponse call +func ParseListImagesResponse(rsp *http.Response) (*ListImagesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListImagesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Image + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseCreateImageResponse parses an HTTP response from a CreateImageWithResponse call +func ParseCreateImageResponse(rsp *http.Response) (*CreateImageResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateImageResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Image + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseDeleteImageResponse parses an HTTP response from a DeleteImageWithResponse call +func ParseDeleteImageResponse(rsp *http.Response) (*DeleteImageResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteImageResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseGetImageResponse parses an HTTP response from a GetImageWithResponse call +func ParseGetImageResponse(rsp *http.Response) (*GetImageResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetImageResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Image + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseListInstancesResponse parses an HTTP response from a ListInstancesWithResponse call +func ParseListInstancesResponse(rsp *http.Response) (*ListInstancesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListInstancesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseCreateInstanceResponse parses an HTTP response from a CreateInstanceWithResponse call +func ParseCreateInstanceResponse(rsp *http.Response) (*CreateInstanceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateInstanceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseDeleteInstanceResponse parses an HTTP response from a DeleteInstanceWithResponse call +func ParseDeleteInstanceResponse(rsp *http.Response) (*DeleteInstanceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteInstanceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseGetInstanceResponse parses an HTTP response from a GetInstanceWithResponse call +func ParseGetInstanceResponse(rsp *http.Response) (*GetInstanceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetInstanceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseGetInstanceLogsResponse parses an HTTP response from a GetInstanceLogsWithResponse call +func ParseGetInstanceLogsResponse(rsp *http.Response) (*GetInstanceLogsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetInstanceLogsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseRestoreInstanceResponse parses an HTTP response from a RestoreInstanceWithResponse call +func ParseRestoreInstanceResponse(rsp *http.Response) (*RestoreInstanceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &RestoreInstanceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseStandbyInstanceResponse parses an HTTP response from a StandbyInstanceWithResponse call +func ParseStandbyInstanceResponse(rsp *http.Response) (*StandbyInstanceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &StandbyInstanceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseDetachVolumeResponse parses an HTTP response from a DetachVolumeWithResponse call +func ParseDetachVolumeResponse(rsp *http.Response) (*DetachVolumeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DetachVolumeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseAttachVolumeResponse parses an HTTP response from a AttachVolumeWithResponse call +func ParseAttachVolumeResponse(rsp *http.Response) (*AttachVolumeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &AttachVolumeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseListVolumesResponse parses an HTTP response from a ListVolumesWithResponse call +func ParseListVolumesResponse(rsp *http.Response) (*ListVolumesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ListVolumesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest []Volume + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseCreateVolumeResponse parses an HTTP response from a CreateVolumeWithResponse call +func ParseCreateVolumeResponse(rsp *http.Response) (*CreateVolumeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateVolumeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Volume + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON401 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseDeleteVolumeResponse parses an HTTP response from a DeleteVolumeWithResponse call +func ParseDeleteVolumeResponse(rsp *http.Response) (*DeleteVolumeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteVolumeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ParseGetVolumeResponse parses an HTTP response from a GetVolumeWithResponse call +func ParseGetVolumeResponse(rsp *http.Response) (*GetVolumeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetVolumeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Volume + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + +// ServerInterface represents all server handlers. +type ServerInterface interface { + // Health check + // (GET /health) + GetHealth(w http.ResponseWriter, r *http.Request) + // List images + // (GET /images) + ListImages(w http.ResponseWriter, r *http.Request) + // Pull and convert OCI image + // (POST /images) + CreateImage(w http.ResponseWriter, r *http.Request) + // Delete image + // (DELETE /images/{id}) + DeleteImage(w http.ResponseWriter, r *http.Request, id string) + // Get image details + // (GET /images/{id}) + GetImage(w http.ResponseWriter, r *http.Request, id string) + // List instances + // (GET /instances) + ListInstances(w http.ResponseWriter, r *http.Request) + // Create and start instance + // (POST /instances) + CreateInstance(w http.ResponseWriter, r *http.Request) + // Stop and delete instance + // (DELETE /instances/{id}) + DeleteInstance(w http.ResponseWriter, r *http.Request, id string) + // Get instance details + // (GET /instances/{id}) + GetInstance(w http.ResponseWriter, r *http.Request, id string) + // Stream instance logs (SSE) + // (GET /instances/{id}/logs) + GetInstanceLogs(w http.ResponseWriter, r *http.Request, id string, params GetInstanceLogsParams) + // Restore instance from standby + // (POST /instances/{id}/restore) + RestoreInstance(w http.ResponseWriter, r *http.Request, id string) + // Put instance in standby (pause, snapshot, delete VMM) + // (POST /instances/{id}/standby) + StandbyInstance(w http.ResponseWriter, r *http.Request, id string) + // Detach volume from instance + // (DELETE /instances/{id}/volumes/{volumeId}) + DetachVolume(w http.ResponseWriter, r *http.Request, id string, volumeId string) + // Attach volume to instance + // (POST /instances/{id}/volumes/{volumeId}) + AttachVolume(w http.ResponseWriter, r *http.Request, id string, volumeId string) + // List volumes + // (GET /volumes) + ListVolumes(w http.ResponseWriter, r *http.Request) + // Create volume + // (POST /volumes) + CreateVolume(w http.ResponseWriter, r *http.Request) + // Delete volume + // (DELETE /volumes/{id}) + DeleteVolume(w http.ResponseWriter, r *http.Request, id string) + // Get volume details + // (GET /volumes/{id}) + GetVolume(w http.ResponseWriter, r *http.Request, id string) +} + +// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint. + +type Unimplemented struct{} + +// Health check +// (GET /health) +func (_ Unimplemented) GetHealth(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// List images +// (GET /images) +func (_ Unimplemented) ListImages(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Pull and convert OCI image +// (POST /images) +func (_ Unimplemented) CreateImage(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Delete image +// (DELETE /images/{id}) +func (_ Unimplemented) DeleteImage(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Get image details +// (GET /images/{id}) +func (_ Unimplemented) GetImage(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// List instances +// (GET /instances) +func (_ Unimplemented) ListInstances(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Create and start instance +// (POST /instances) +func (_ Unimplemented) CreateInstance(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Stop and delete instance +// (DELETE /instances/{id}) +func (_ Unimplemented) DeleteInstance(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Get instance details +// (GET /instances/{id}) +func (_ Unimplemented) GetInstance(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Stream instance logs (SSE) +// (GET /instances/{id}/logs) +func (_ Unimplemented) GetInstanceLogs(w http.ResponseWriter, r *http.Request, id string, params GetInstanceLogsParams) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Restore instance from standby +// (POST /instances/{id}/restore) +func (_ Unimplemented) RestoreInstance(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Put instance in standby (pause, snapshot, delete VMM) +// (POST /instances/{id}/standby) +func (_ Unimplemented) StandbyInstance(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Detach volume from instance +// (DELETE /instances/{id}/volumes/{volumeId}) +func (_ Unimplemented) DetachVolume(w http.ResponseWriter, r *http.Request, id string, volumeId string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Attach volume to instance +// (POST /instances/{id}/volumes/{volumeId}) +func (_ Unimplemented) AttachVolume(w http.ResponseWriter, r *http.Request, id string, volumeId string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// List volumes +// (GET /volumes) +func (_ Unimplemented) ListVolumes(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Create volume +// (POST /volumes) +func (_ Unimplemented) CreateVolume(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Delete volume +// (DELETE /volumes/{id}) +func (_ Unimplemented) DeleteVolume(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// Get volume details +// (GET /volumes/{id}) +func (_ Unimplemented) GetVolume(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + +// ServerInterfaceWrapper converts contexts to parameters. +type ServerInterfaceWrapper struct { + Handler ServerInterface + HandlerMiddlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +type MiddlewareFunc func(http.Handler) http.Handler + +// GetHealth operation middleware +func (siw *ServerInterfaceWrapper) GetHealth(w http.ResponseWriter, r *http.Request) { + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetHealth(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListImages operation middleware +func (siw *ServerInterfaceWrapper) ListImages(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListImages(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateImage operation middleware +func (siw *ServerInterfaceWrapper) CreateImage(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateImage(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteImage operation middleware +func (siw *ServerInterfaceWrapper) DeleteImage(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteImage(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetImage operation middleware +func (siw *ServerInterfaceWrapper) GetImage(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetImage(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListInstances operation middleware +func (siw *ServerInterfaceWrapper) ListInstances(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListInstances(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateInstance operation middleware +func (siw *ServerInterfaceWrapper) CreateInstance(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateInstance(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteInstance operation middleware +func (siw *ServerInterfaceWrapper) DeleteInstance(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteInstance(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetInstance operation middleware +func (siw *ServerInterfaceWrapper) GetInstance(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetInstance(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetInstanceLogs operation middleware +func (siw *ServerInterfaceWrapper) GetInstanceLogs(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + // Parameter object where we will unmarshal all parameters from the context + var params GetInstanceLogsParams + + // ------------- Optional query parameter "follow" ------------- + + err = runtime.BindQueryParameter("form", true, false, "follow", r.URL.Query(), ¶ms.Follow) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "follow", Err: err}) + return + } + + // ------------- Optional query parameter "tail" ------------- + + err = runtime.BindQueryParameter("form", true, false, "tail", r.URL.Query(), ¶ms.Tail) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "tail", Err: err}) + return + } + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetInstanceLogs(w, r, id, params) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// RestoreInstance operation middleware +func (siw *ServerInterfaceWrapper) RestoreInstance(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.RestoreInstance(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// StandbyInstance operation middleware +func (siw *ServerInterfaceWrapper) StandbyInstance(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.StandbyInstance(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DetachVolume operation middleware +func (siw *ServerInterfaceWrapper) DetachVolume(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "volumeId" ------------- + var volumeId string + + err = runtime.BindStyledParameterWithOptions("simple", "volumeId", chi.URLParam(r, "volumeId"), &volumeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "volumeId", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DetachVolume(w, r, id, volumeId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// AttachVolume operation middleware +func (siw *ServerInterfaceWrapper) AttachVolume(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + // ------------- Path parameter "volumeId" ------------- + var volumeId string + + err = runtime.BindStyledParameterWithOptions("simple", "volumeId", chi.URLParam(r, "volumeId"), &volumeId, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "volumeId", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.AttachVolume(w, r, id, volumeId) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// ListVolumes operation middleware +func (siw *ServerInterfaceWrapper) ListVolumes(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.ListVolumes(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// CreateVolume operation middleware +func (siw *ServerInterfaceWrapper) CreateVolume(w http.ResponseWriter, r *http.Request) { + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.CreateVolume(w, r) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// DeleteVolume operation middleware +func (siw *ServerInterfaceWrapper) DeleteVolume(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.DeleteVolume(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +// GetVolume operation middleware +func (siw *ServerInterfaceWrapper) GetVolume(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.GetVolume(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + +type UnescapedCookieParamError struct { + ParamName string + Err error +} + +func (e *UnescapedCookieParamError) Error() string { + return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName) +} + +func (e *UnescapedCookieParamError) Unwrap() error { + return e.Err +} + +type UnmarshalingParamError struct { + ParamName string + Err error +} + +func (e *UnmarshalingParamError) Error() string { + return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error()) +} + +func (e *UnmarshalingParamError) Unwrap() error { + return e.Err +} + +type RequiredParamError struct { + ParamName string +} + +func (e *RequiredParamError) Error() string { + return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName) +} + +type RequiredHeaderError struct { + ParamName string + Err error +} + +func (e *RequiredHeaderError) Error() string { + return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName) +} + +func (e *RequiredHeaderError) Unwrap() error { + return e.Err +} + +type InvalidParamFormatError struct { + ParamName string + Err error +} + +func (e *InvalidParamFormatError) Error() string { + return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error()) +} + +func (e *InvalidParamFormatError) Unwrap() error { + return e.Err +} + +type TooManyValuesForParamError struct { + ParamName string + Count int +} + +func (e *TooManyValuesForParamError) Error() string { + return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count) +} + +// Handler creates http.Handler with routing matching OpenAPI spec. +func Handler(si ServerInterface) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{}) +} + +type ChiServerOptions struct { + BaseURL string + BaseRouter chi.Router + Middlewares []MiddlewareFunc + ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux. +func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseRouter: r, + }) +} + +func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler { + return HandlerWithOptions(si, ChiServerOptions{ + BaseURL: baseURL, + BaseRouter: r, + }) +} + +// HandlerWithOptions creates http.Handler with additional options +func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler { + r := options.BaseRouter + + if r == nil { + r = chi.NewRouter() + } + if options.ErrorHandlerFunc == nil { + options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + } + } + wrapper := ServerInterfaceWrapper{ + Handler: si, + HandlerMiddlewares: options.Middlewares, + ErrorHandlerFunc: options.ErrorHandlerFunc, + } + + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/health", wrapper.GetHealth) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/images", wrapper.ListImages) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/images", wrapper.CreateImage) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/images/{id}", wrapper.DeleteImage) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/images/{id}", wrapper.GetImage) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/instances", wrapper.ListInstances) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/instances", wrapper.CreateInstance) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/instances/{id}", wrapper.DeleteInstance) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/instances/{id}", wrapper.GetInstance) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/instances/{id}/logs", wrapper.GetInstanceLogs) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/instances/{id}/restore", wrapper.RestoreInstance) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/instances/{id}/standby", wrapper.StandbyInstance) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/instances/{id}/volumes/{volumeId}", wrapper.DetachVolume) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/instances/{id}/volumes/{volumeId}", wrapper.AttachVolume) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/volumes", wrapper.ListVolumes) + }) + r.Group(func(r chi.Router) { + r.Post(options.BaseURL+"/volumes", wrapper.CreateVolume) + }) + r.Group(func(r chi.Router) { + r.Delete(options.BaseURL+"/volumes/{id}", wrapper.DeleteVolume) + }) + r.Group(func(r chi.Router) { + r.Get(options.BaseURL+"/volumes/{id}", wrapper.GetVolume) + }) + + return r +} + +type GetHealthRequestObject struct { +} + +type GetHealthResponseObject interface { + VisitGetHealthResponse(w http.ResponseWriter) error +} + +type GetHealth200JSONResponse Health + +func (response GetHealth200JSONResponse) VisitGetHealthResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ListImagesRequestObject struct { +} + +type ListImagesResponseObject interface { + VisitListImagesResponse(w http.ResponseWriter) error +} + +type ListImages200JSONResponse []Image + +func (response ListImages200JSONResponse) VisitListImagesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ListImages401JSONResponse Error + +func (response ListImages401JSONResponse) VisitListImagesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type ListImages500JSONResponse Error + +func (response ListImages500JSONResponse) VisitListImagesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type CreateImageRequestObject struct { + Body *CreateImageJSONRequestBody +} + +type CreateImageResponseObject interface { + VisitCreateImageResponse(w http.ResponseWriter) error +} + +type CreateImage201JSONResponse Image + +func (response CreateImage201JSONResponse) VisitCreateImageResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +type CreateImage400JSONResponse Error + +func (response CreateImage400JSONResponse) VisitCreateImageResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CreateImage401JSONResponse Error + +func (response CreateImage401JSONResponse) VisitCreateImageResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type CreateImage500JSONResponse Error + +func (response CreateImage500JSONResponse) VisitCreateImageResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteImageRequestObject struct { + Id string `json:"id"` +} + +type DeleteImageResponseObject interface { + VisitDeleteImageResponse(w http.ResponseWriter) error +} + +type DeleteImage204Response struct { +} + +func (response DeleteImage204Response) VisitDeleteImageResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type DeleteImage404JSONResponse Error + +func (response DeleteImage404JSONResponse) VisitDeleteImageResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteImage500JSONResponse Error + +func (response DeleteImage500JSONResponse) VisitDeleteImageResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetImageRequestObject struct { + Id string `json:"id"` +} + +type GetImageResponseObject interface { + VisitGetImageResponse(w http.ResponseWriter) error +} + +type GetImage200JSONResponse Image + +func (response GetImage200JSONResponse) VisitGetImageResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetImage404JSONResponse Error + +func (response GetImage404JSONResponse) VisitGetImageResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetImage500JSONResponse Error + +func (response GetImage500JSONResponse) VisitGetImageResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type ListInstancesRequestObject struct { +} + +type ListInstancesResponseObject interface { + VisitListInstancesResponse(w http.ResponseWriter) error +} + +type ListInstances200JSONResponse []Instance + +func (response ListInstances200JSONResponse) VisitListInstancesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ListInstances401JSONResponse Error + +func (response ListInstances401JSONResponse) VisitListInstancesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type ListInstances500JSONResponse Error + +func (response ListInstances500JSONResponse) VisitListInstancesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type CreateInstanceRequestObject struct { + Body *CreateInstanceJSONRequestBody +} + +type CreateInstanceResponseObject interface { + VisitCreateInstanceResponse(w http.ResponseWriter) error +} + +type CreateInstance201JSONResponse Instance + +func (response CreateInstance201JSONResponse) VisitCreateInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +type CreateInstance400JSONResponse Error + +func (response CreateInstance400JSONResponse) VisitCreateInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CreateInstance401JSONResponse Error + +func (response CreateInstance401JSONResponse) VisitCreateInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type CreateInstance500JSONResponse Error + +func (response CreateInstance500JSONResponse) VisitCreateInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteInstanceRequestObject struct { + Id string `json:"id"` +} + +type DeleteInstanceResponseObject interface { + VisitDeleteInstanceResponse(w http.ResponseWriter) error +} + +type DeleteInstance204Response struct { +} + +func (response DeleteInstance204Response) VisitDeleteInstanceResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type DeleteInstance404JSONResponse Error + +func (response DeleteInstance404JSONResponse) VisitDeleteInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteInstance500JSONResponse Error + +func (response DeleteInstance500JSONResponse) VisitDeleteInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetInstanceRequestObject struct { + Id string `json:"id"` +} + +type GetInstanceResponseObject interface { + VisitGetInstanceResponse(w http.ResponseWriter) error +} + +type GetInstance200JSONResponse Instance + +func (response GetInstance200JSONResponse) VisitGetInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetInstance404JSONResponse Error + +func (response GetInstance404JSONResponse) VisitGetInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetInstance500JSONResponse Error + +func (response GetInstance500JSONResponse) VisitGetInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetInstanceLogsRequestObject struct { + Id string `json:"id"` + Params GetInstanceLogsParams +} + +type GetInstanceLogsResponseObject interface { + VisitGetInstanceLogsResponse(w http.ResponseWriter) error +} + +type GetInstanceLogs200TexteventStreamResponse struct { + Body io.Reader + ContentLength int64 +} + +func (response GetInstanceLogs200TexteventStreamResponse) VisitGetInstanceLogsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/event-stream") + if response.ContentLength != 0 { + w.Header().Set("Content-Length", fmt.Sprint(response.ContentLength)) + } + w.WriteHeader(200) + + if closer, ok := response.Body.(io.ReadCloser); ok { + defer closer.Close() + } + _, err := io.Copy(w, response.Body) + return err +} + +type GetInstanceLogs200TextResponse string + +func (response GetInstanceLogs200TextResponse) VisitGetInstanceLogsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "text/plain") + w.WriteHeader(200) + + _, err := w.Write([]byte(response)) + return err +} + +type GetInstanceLogs404JSONResponse Error + +func (response GetInstanceLogs404JSONResponse) VisitGetInstanceLogsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetInstanceLogs500JSONResponse Error + +func (response GetInstanceLogs500JSONResponse) VisitGetInstanceLogsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type RestoreInstanceRequestObject struct { + Id string `json:"id"` +} + +type RestoreInstanceResponseObject interface { + VisitRestoreInstanceResponse(w http.ResponseWriter) error +} + +type RestoreInstance200JSONResponse Instance + +func (response RestoreInstance200JSONResponse) VisitRestoreInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type RestoreInstance404JSONResponse Error + +func (response RestoreInstance404JSONResponse) VisitRestoreInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type RestoreInstance409JSONResponse Error + +func (response RestoreInstance409JSONResponse) VisitRestoreInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type RestoreInstance500JSONResponse Error + +func (response RestoreInstance500JSONResponse) VisitRestoreInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type StandbyInstanceRequestObject struct { + Id string `json:"id"` +} + +type StandbyInstanceResponseObject interface { + VisitStandbyInstanceResponse(w http.ResponseWriter) error +} + +type StandbyInstance200JSONResponse Instance + +func (response StandbyInstance200JSONResponse) VisitStandbyInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type StandbyInstance404JSONResponse Error + +func (response StandbyInstance404JSONResponse) VisitStandbyInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type StandbyInstance409JSONResponse Error + +func (response StandbyInstance409JSONResponse) VisitStandbyInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type StandbyInstance500JSONResponse Error + +func (response StandbyInstance500JSONResponse) VisitStandbyInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type DetachVolumeRequestObject struct { + Id string `json:"id"` + VolumeId string `json:"volumeId"` +} + +type DetachVolumeResponseObject interface { + VisitDetachVolumeResponse(w http.ResponseWriter) error +} + +type DetachVolume200JSONResponse Instance + +func (response DetachVolume200JSONResponse) VisitDetachVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type DetachVolume404JSONResponse Error + +func (response DetachVolume404JSONResponse) VisitDetachVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type DetachVolume500JSONResponse Error + +func (response DetachVolume500JSONResponse) VisitDetachVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type AttachVolumeRequestObject struct { + Id string `json:"id"` + VolumeId string `json:"volumeId"` + Body *AttachVolumeJSONRequestBody +} + +type AttachVolumeResponseObject interface { + VisitAttachVolumeResponse(w http.ResponseWriter) error +} + +type AttachVolume200JSONResponse Instance + +func (response AttachVolume200JSONResponse) VisitAttachVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type AttachVolume404JSONResponse Error + +func (response AttachVolume404JSONResponse) VisitAttachVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type AttachVolume409JSONResponse Error + +func (response AttachVolume409JSONResponse) VisitAttachVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type AttachVolume500JSONResponse Error + +func (response AttachVolume500JSONResponse) VisitAttachVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type ListVolumesRequestObject struct { +} + +type ListVolumesResponseObject interface { + VisitListVolumesResponse(w http.ResponseWriter) error +} + +type ListVolumes200JSONResponse []Volume + +func (response ListVolumes200JSONResponse) VisitListVolumesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ListVolumes401JSONResponse Error + +func (response ListVolumes401JSONResponse) VisitListVolumesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type ListVolumes500JSONResponse Error + +func (response ListVolumes500JSONResponse) VisitListVolumesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVolumeRequestObject struct { + Body *CreateVolumeJSONRequestBody +} + +type CreateVolumeResponseObject interface { + VisitCreateVolumeResponse(w http.ResponseWriter) error +} + +type CreateVolume201JSONResponse Volume + +func (response CreateVolume201JSONResponse) VisitCreateVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(201) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVolume400JSONResponse Error + +func (response CreateVolume400JSONResponse) VisitCreateVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVolume401JSONResponse Error + +func (response CreateVolume401JSONResponse) VisitCreateVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) +} + +type CreateVolume500JSONResponse Error + +func (response CreateVolume500JSONResponse) VisitCreateVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteVolumeRequestObject struct { + Id string `json:"id"` +} + +type DeleteVolumeResponseObject interface { + VisitDeleteVolumeResponse(w http.ResponseWriter) error +} + +type DeleteVolume204Response struct { +} + +func (response DeleteVolume204Response) VisitDeleteVolumeResponse(w http.ResponseWriter) error { + w.WriteHeader(204) + return nil +} + +type DeleteVolume404JSONResponse Error + +func (response DeleteVolume404JSONResponse) VisitDeleteVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteVolume409JSONResponse Error + +func (response DeleteVolume409JSONResponse) VisitDeleteVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type DeleteVolume500JSONResponse Error + +func (response DeleteVolume500JSONResponse) VisitDeleteVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +type GetVolumeRequestObject struct { + Id string `json:"id"` +} + +type GetVolumeResponseObject interface { + VisitGetVolumeResponse(w http.ResponseWriter) error +} + +type GetVolume200JSONResponse Volume + +func (response GetVolume200JSONResponse) VisitGetVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetVolume404JSONResponse Error + +func (response GetVolume404JSONResponse) VisitGetVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type GetVolume500JSONResponse Error + +func (response GetVolume500JSONResponse) VisitGetVolumeResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + +// StrictServerInterface represents all server handlers. +type StrictServerInterface interface { + // Health check + // (GET /health) + GetHealth(ctx context.Context, request GetHealthRequestObject) (GetHealthResponseObject, error) + // List images + // (GET /images) + ListImages(ctx context.Context, request ListImagesRequestObject) (ListImagesResponseObject, error) + // Pull and convert OCI image + // (POST /images) + CreateImage(ctx context.Context, request CreateImageRequestObject) (CreateImageResponseObject, error) + // Delete image + // (DELETE /images/{id}) + DeleteImage(ctx context.Context, request DeleteImageRequestObject) (DeleteImageResponseObject, error) + // Get image details + // (GET /images/{id}) + GetImage(ctx context.Context, request GetImageRequestObject) (GetImageResponseObject, error) + // List instances + // (GET /instances) + ListInstances(ctx context.Context, request ListInstancesRequestObject) (ListInstancesResponseObject, error) + // Create and start instance + // (POST /instances) + CreateInstance(ctx context.Context, request CreateInstanceRequestObject) (CreateInstanceResponseObject, error) + // Stop and delete instance + // (DELETE /instances/{id}) + DeleteInstance(ctx context.Context, request DeleteInstanceRequestObject) (DeleteInstanceResponseObject, error) + // Get instance details + // (GET /instances/{id}) + GetInstance(ctx context.Context, request GetInstanceRequestObject) (GetInstanceResponseObject, error) + // Stream instance logs (SSE) + // (GET /instances/{id}/logs) + GetInstanceLogs(ctx context.Context, request GetInstanceLogsRequestObject) (GetInstanceLogsResponseObject, error) + // Restore instance from standby + // (POST /instances/{id}/restore) + RestoreInstance(ctx context.Context, request RestoreInstanceRequestObject) (RestoreInstanceResponseObject, error) + // Put instance in standby (pause, snapshot, delete VMM) + // (POST /instances/{id}/standby) + StandbyInstance(ctx context.Context, request StandbyInstanceRequestObject) (StandbyInstanceResponseObject, error) + // Detach volume from instance + // (DELETE /instances/{id}/volumes/{volumeId}) + DetachVolume(ctx context.Context, request DetachVolumeRequestObject) (DetachVolumeResponseObject, error) + // Attach volume to instance + // (POST /instances/{id}/volumes/{volumeId}) + AttachVolume(ctx context.Context, request AttachVolumeRequestObject) (AttachVolumeResponseObject, error) + // List volumes + // (GET /volumes) + ListVolumes(ctx context.Context, request ListVolumesRequestObject) (ListVolumesResponseObject, error) + // Create volume + // (POST /volumes) + CreateVolume(ctx context.Context, request CreateVolumeRequestObject) (CreateVolumeResponseObject, error) + // Delete volume + // (DELETE /volumes/{id}) + DeleteVolume(ctx context.Context, request DeleteVolumeRequestObject) (DeleteVolumeResponseObject, error) + // Get volume details + // (GET /volumes/{id}) + GetVolume(ctx context.Context, request GetVolumeRequestObject) (GetVolumeResponseObject, error) +} + +type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc +type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc + +type StrictHTTPServerOptions struct { + RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) + ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error) +} + +func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{ + RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusBadRequest) + }, + ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, err.Error(), http.StatusInternalServerError) + }, + }} +} + +func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface { + return &strictHandler{ssi: ssi, middlewares: middlewares, options: options} +} + +type strictHandler struct { + ssi StrictServerInterface + middlewares []StrictMiddlewareFunc + options StrictHTTPServerOptions +} + +// GetHealth operation middleware +func (sh *strictHandler) GetHealth(w http.ResponseWriter, r *http.Request) { + var request GetHealthRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetHealth(ctx, request.(GetHealthRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetHealth") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetHealthResponseObject); ok { + if err := validResponse.VisitGetHealthResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ListImages operation middleware +func (sh *strictHandler) ListImages(w http.ResponseWriter, r *http.Request) { + var request ListImagesRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ListImages(ctx, request.(ListImagesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListImages") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ListImagesResponseObject); ok { + if err := validResponse.VisitListImagesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// CreateImage operation middleware +func (sh *strictHandler) CreateImage(w http.ResponseWriter, r *http.Request) { + var request CreateImageRequestObject + + var body CreateImageJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.CreateImage(ctx, request.(CreateImageRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateImage") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(CreateImageResponseObject); ok { + if err := validResponse.VisitCreateImageResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// DeleteImage operation middleware +func (sh *strictHandler) DeleteImage(w http.ResponseWriter, r *http.Request, id string) { + var request DeleteImageRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DeleteImage(ctx, request.(DeleteImageRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteImage") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DeleteImageResponseObject); ok { + if err := validResponse.VisitDeleteImageResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// GetImage operation middleware +func (sh *strictHandler) GetImage(w http.ResponseWriter, r *http.Request, id string) { + var request GetImageRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetImage(ctx, request.(GetImageRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetImage") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetImageResponseObject); ok { + if err := validResponse.VisitGetImageResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ListInstances operation middleware +func (sh *strictHandler) ListInstances(w http.ResponseWriter, r *http.Request) { + var request ListInstancesRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ListInstances(ctx, request.(ListInstancesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListInstances") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ListInstancesResponseObject); ok { + if err := validResponse.VisitListInstancesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// CreateInstance operation middleware +func (sh *strictHandler) CreateInstance(w http.ResponseWriter, r *http.Request) { + var request CreateInstanceRequestObject + + var body CreateInstanceJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.CreateInstance(ctx, request.(CreateInstanceRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateInstance") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(CreateInstanceResponseObject); ok { + if err := validResponse.VisitCreateInstanceResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// DeleteInstance operation middleware +func (sh *strictHandler) DeleteInstance(w http.ResponseWriter, r *http.Request, id string) { + var request DeleteInstanceRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DeleteInstance(ctx, request.(DeleteInstanceRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteInstance") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DeleteInstanceResponseObject); ok { + if err := validResponse.VisitDeleteInstanceResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// GetInstance operation middleware +func (sh *strictHandler) GetInstance(w http.ResponseWriter, r *http.Request, id string) { + var request GetInstanceRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetInstance(ctx, request.(GetInstanceRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetInstance") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetInstanceResponseObject); ok { + if err := validResponse.VisitGetInstanceResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// GetInstanceLogs operation middleware +func (sh *strictHandler) GetInstanceLogs(w http.ResponseWriter, r *http.Request, id string, params GetInstanceLogsParams) { + var request GetInstanceLogsRequestObject + + request.Id = id + request.Params = params + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetInstanceLogs(ctx, request.(GetInstanceLogsRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetInstanceLogs") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetInstanceLogsResponseObject); ok { + if err := validResponse.VisitGetInstanceLogsResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// RestoreInstance operation middleware +func (sh *strictHandler) RestoreInstance(w http.ResponseWriter, r *http.Request, id string) { + var request RestoreInstanceRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.RestoreInstance(ctx, request.(RestoreInstanceRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "RestoreInstance") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(RestoreInstanceResponseObject); ok { + if err := validResponse.VisitRestoreInstanceResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// StandbyInstance operation middleware +func (sh *strictHandler) StandbyInstance(w http.ResponseWriter, r *http.Request, id string) { + var request StandbyInstanceRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.StandbyInstance(ctx, request.(StandbyInstanceRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "StandbyInstance") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(StandbyInstanceResponseObject); ok { + if err := validResponse.VisitStandbyInstanceResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// DetachVolume operation middleware +func (sh *strictHandler) DetachVolume(w http.ResponseWriter, r *http.Request, id string, volumeId string) { + var request DetachVolumeRequestObject + + request.Id = id + request.VolumeId = volumeId + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DetachVolume(ctx, request.(DetachVolumeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DetachVolume") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DetachVolumeResponseObject); ok { + if err := validResponse.VisitDetachVolumeResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// AttachVolume operation middleware +func (sh *strictHandler) AttachVolume(w http.ResponseWriter, r *http.Request, id string, volumeId string) { + var request AttachVolumeRequestObject + + request.Id = id + request.VolumeId = volumeId + + var body AttachVolumeJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.AttachVolume(ctx, request.(AttachVolumeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "AttachVolume") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(AttachVolumeResponseObject); ok { + if err := validResponse.VisitAttachVolumeResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// ListVolumes operation middleware +func (sh *strictHandler) ListVolumes(w http.ResponseWriter, r *http.Request) { + var request ListVolumesRequestObject + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.ListVolumes(ctx, request.(ListVolumesRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "ListVolumes") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(ListVolumesResponseObject); ok { + if err := validResponse.VisitListVolumesResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// CreateVolume operation middleware +func (sh *strictHandler) CreateVolume(w http.ResponseWriter, r *http.Request) { + var request CreateVolumeRequestObject + + var body CreateVolumeJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.CreateVolume(ctx, request.(CreateVolumeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "CreateVolume") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(CreateVolumeResponseObject); ok { + if err := validResponse.VisitCreateVolumeResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// DeleteVolume operation middleware +func (sh *strictHandler) DeleteVolume(w http.ResponseWriter, r *http.Request, id string) { + var request DeleteVolumeRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.DeleteVolume(ctx, request.(DeleteVolumeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "DeleteVolume") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(DeleteVolumeResponseObject); ok { + if err := validResponse.VisitDeleteVolumeResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// GetVolume operation middleware +func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id string) { + var request GetVolumeRequestObject + + request.Id = id + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.GetVolume(ctx, request.(GetVolumeRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetVolume") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(GetVolumeResponseObject); ok { + if err := validResponse.VisitGetVolumeResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + +// Base64 encoded, gzipped, json marshaled Swagger object +var swaggerSpec = []string{ + + "H4sIAAAAAAAC/+xcbW8TuRb+K5bv/VCkpEnawkLuJyjsUmkLFYWudFkUOeOTjBePPdieQED971d+mcxM", + "xnkptFmyt9JK22Q859iPn/Picxy+4URmuRQgjMbDb1gnKWTE/fnUGJKkV5IXGbyBTwVoY7/OlcxBGQZu", + "UCYLYUY5Man9REEniuWGSYGH+IKYFH1OQQGaOSlIp7LgFI0BufeA4g6GLyTLOeAh7mXC9CgxBHewmef2", + "K20UE1N83cEKCJWCz72aCSm4wcMJ4Ro6S2rPrWhENLKvdN07C3ljKTkQga+dxE8FU0Dx8H19GR8Wg+X4", + "L0iMVX6qgBg4y8h0NRKMthF47f4gHCWFNjJDjIIwbMJAoQNSGNmdggBFDFDEJkhIg3IlZ4wCfdBAhmXT", + "rpgy8aU7G8TAESSDiPbTM8TsnJGCCSgQCaADOJwedhCVyUdQh0z2OBsrouY9J37IiQFtmsrXj21PZwla", + "N7c1oAptiEhW4wpiZv9HKGUezIvG4xYWTQxeiBlTUmQgDJoRxciYg64v7xt+9fr5i9GLV1d4aDXTInGv", + "dvDF6zdv8RAf9/t9K7c1/9iGvxPsUwH1fZ5IhUwKiIV1ooNyi9F4jhLCOailzRbadMk4GRwdx/ba7Whb", + "syNnTXGLP0mqZAYrCJRBJtV8lJEvo2zcMLGT/pNHLQsjX1hWZMi/hT4zk6JUmpwXU8QEOn9WV+4FBI1M", + "GJiCqqtsqhv0j06W1T0jGkpdLfFH/ZPHMfFxk3hZZER0rWOwREBuUB2obN79LNVHLgntRoHKpTKjjOQ5", + "E1MdcXlSGVQ+RhMlM5RKbZCRaFp4a2EGMvfmvxVM8BD/q1d54F5wvz0r59yLqXGPKEXm7jPLQBZmpCGR", + "guoGgseP+v1lBN/68Y6MOiEcukZ2v4KSSENGhGFJwyZ+ObIi2pjOkrxoKjta1vSqyMagkJygGVOmIByd", + "XrxrCD+KSnbxIQKoDz/aAkhcPNoWQf+ij2HW+tswLvkpZoNRIIS3sdVOa0NQvMtQMJO8a2Nk9waBwE83", + "ynYnyqMfk6fZVxhNx22Rl+yr9WloyqZkPDdNnzqIsCcWFSr5MahfKCVVG9xE0sgSn+Y5Zwmxn7o6h4RN", + "WILASkD2BXSQkSRlAha230R1TOhIhe3sxGKKIYxH6Pl0EZWCsjASHVhTywpuWM7BP9MPtuWuW/lzJylm", + "/UwIUCMo4bmBpAy0jkaPJb9YrmUxxHkOCuNiOrWQ1KE7Z1ozMUXl7qIJA06HPvPYmB243awmtpIHYQ1b", + "suF3+RlUl8MMeJ0E3qLsZDOpAC144jdtKQjPCGd0xERemHjEXAHlr4UyKZRMQGRsHa/NAPyG1ZX4mG1t", + "fSILQaNgteB4CYT7bLuJhDbEFCFjKjKLrfxo8azUyY8btyMIiW3DWZl3LG1AFnF2p+fPffBLpDCECVAo", + "A0NCbr+Y0XvsMkncwV3LKUogkwLJyeQ/dgYLU2l7uYJzy1M8NKqAtoEkzknTETGRqdlnltE2hmpDshwd", + "vPn19Pj4+EnTJRz1jx52+4Pu4OHbQX/Yt//9F3fwRKrMysWUGOhaITF2gDBqnksmIjN4sXi2HUY9n4B3", + "K5mHOv0xgO4gp95mLd/wxdO3L+1Jr9Cqx2VCeE+PmRjWPi8+Vg/cH/7jmInvz8Xv5kj1Q2elEAF9BG1p", + "e870R6RDpG1F2YfHjx7/0n8yOKqRkgnz6ARHMyxQ2kmNHxwMmSLr4tnUz7Na0mLqK0hVLcVmzkxMR5Sp", + "tpo//ENEmYLE2FR+C8LgHsnzzarXZHI1VxB1auFUFvFrt+9Djm/qQ+7i4NuCYPKJilgU43yOPhWEW9Oh", + "iMqMMNFOIWuH1UNnwNsQJSV6pAXJdSoj6P6RggugBJVjEHxh2uhwlmZ6cZiuTyXUgpYLPd/lG36OE3iD", + "clJM2LSw+VXWPH1/74F7hfTxnh62b+lknSs2IwZGLI/o88/Q2QUilCrQDXeMB0+ODgePHh8O+v3DQX8b", + "O9CGqFU+5tI++w4H83Clg9lmOgY24Vd6zEs32L0l83zlImR+ozUcbXCSG9cQrYzESiFJoDwJ1b6bFD/u", + "suDhKxZAUTlid/WOkgFbR83LkjBLjrAsdTpxwz9FF/myCR2iq/NzFKSjcWHc4SeYATo45bKg6OU8BzVj", + "WiokiGEzeGAlvCmEYGJqJSCmEUnsEz5Hyn+//uULUmiv3b6bu0/r37hMC0PlZ+He0WlhkP3kpmyXEALS", + "ehHeMIbolXTvhJl2kJDLkc0PJ4KO5+3hy1HwICECjW0Gqo1UQB/86fgbDn0BadzBATHcwX75uIPLVdk/", + "/ezcX05xbacrc6p7y1aK5CqaI+ukV/hmJtzB141DV+d1o3gctbFUrhcovUA7rCksLi5X0shE8kbJEpsk", + "r+HlPxU0j6x/yWKq2XXqa49ZiLfGNmQkWPfIyDV2c/YcsQkqx65JTTa6w9vOYvtPbnoSvnn2tb7Cua7h", + "6Dt/9tlK/Oo9xo3o7U01te7LSyUbvXgrYvxgc5fpsqvbMPy7aPGWR4S25nU93zLqjmKcDLu6hpOrDgRL", + "e1Hp6KxvK1tCQFIoZuaXNoh7zMdAFKinhcfcRXe3CPd1pTw1JsfX164aPIn4kt9AgGIJenpx5o5NGRFk", + "auPk1TnibALJPOGACle5bQUx19B7fXrWtYcBisok3R0fmXGA2NEZEVY+rhUXcP9wcOjapTIHQXKGh/jY", + "fdXBFga3xF66KGFOwdHOks75ojPq5m5CkdMiq3MptMfmqN/3NV9hAl9JVfbv/aV9ecNnRJvypaDBQbhk", + "jBaGxLHKT9TnTrrIMqLmdu3uW5SkkHx0j3ouf9IrF/Q70+bMD/nBFW2VCvpKbTv/a63UzstmrmH61x18", + "0h/cGsK+fxNR+06QwqRSsa9ArdKHt7itK5WeCQNKEI40qBmoUI2vGyEevm+a3/sP1x/q++7gqrDKpY7s", + "de2qCPaOAbR5Jun81pYYuYxy3XRCNpxdt5h2ezsbCBYB2ZVAQtzxfNrB1j4jFJXNu3sOr+fwRcE5IoLa", + "8+8MlEGLinbdk/W+MXrtgwoHf8Rrsvy5+75keU4UycCA0k4/s3N1Qa9MoXyC0iRop4bGckT90CLvyap6", + "m59hINvJDvZgqWe3R3vvN63c7c7K4LvDbe3vyieVHfx7mmykyW8QolwFmvMM4XS6Ic1ZjNpJplP2AW6S", + "7CxmeB8rtsl36nCtTXmqnswdZj1Lt0V3nfgs+BYDPFRv7tOfn5fSnkUuAXJV56qT2PRx2yZAFef/phyo", + "JN3O06BS8V6GONeXsiSgISWqxZGVWdFO97q/W5+18/Ror+njMqQWdG0H0uNyqteVuUoYfpeugX3rvOq0", + "rnVIzuVnZOeFDrRRQDJf7bu8fOGup9pBnwpQ80rnxL2D63qWq7PtX9esbpNyJvx9bgWmUMJfAwJ3+zGm", + "PdzMjOgexPq0W5iSgS+mBzMQpusRaJIqcgXTvpBzwsT6ke2UU05RUHFvWNv5ZcfIhW15njpuxswrdEBd", + "4yKamb7xA/7RrrtsA//NFDvpP7l71adSTDhLDOpWHLGzYMKmc4KO50iqen99n8gfyFqtzHnGsK4o/8tn", + "K/kfWvv/aP5Xe/9/bgGJVAoS42/d7FdNupZO1Uz5wF3UqS7AdMp0/er8PB4Qwp2p3jf/x9mmM1z1A+c7", + "yr4iQsqp7YWVha44hXCVYucWJhdN/j0tuVvgyiU4h14/a8a9dv2H9/vAy9sv9sX+6YGtSn07tYrFBaOf", + "xSp2HYHCHAhXQOi8gce+GKhnWrkSI5cKgrVruitbHleLi7p33/AITuEG7Y5yBfeV4S2aHTWw1rU6Fq75", + "7hod3+H7bm9zS5at9Hz3LY6fvsUxK/ew8mJbNjXuLvHYqqWxSDl329C4+nniKdN7GUrD9ZLZIkStqnrv", + "kmD93TnFXfdQrvb4XPQblMG21j9xAqxET4flWnpCOKIwAy5z96tWPxZ3cKF4uBE97PlfkadSG/erEHz9", + "4fp/AQAA//+BHkAF40wAAA==", +} + +// GetSwagger returns the content of the embedded swagger specification file +// or error if failed to decode +func decodeSpec() ([]byte, error) { + zipped, err := base64.StdEncoding.DecodeString(strings.Join(swaggerSpec, "")) + if err != nil { + return nil, fmt.Errorf("error base64 decoding spec: %w", err) + } + zr, err := gzip.NewReader(bytes.NewReader(zipped)) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + var buf bytes.Buffer + _, err = buf.ReadFrom(zr) + if err != nil { + return nil, fmt.Errorf("error decompressing spec: %w", err) + } + + return buf.Bytes(), nil +} + +var rawSpec = decodeSpecCached() + +// a naive cached of a decoded swagger spec +func decodeSpecCached() func() ([]byte, error) { + data, err := decodeSpec() + return func() ([]byte, error) { + return data, err + } +} + +// Constructs a synthetic filesystem for resolving external references when loading openapi specifications. +func PathToRawSpec(pathToFile string) map[string]func() ([]byte, error) { + res := make(map[string]func() ([]byte, error)) + if len(pathToFile) > 0 { + res[pathToFile] = rawSpec + } + + return res +} + +// GetSwagger returns the Swagger specification corresponding to the generated code +// in this file. The external references of Swagger specification are resolved. +// The logic of resolving external references is tightly connected to "import-mapping" feature. +// Externally referenced files must be embedded in the corresponding golang packages. +// Urls can be supported but this task was out of the scope. +func GetSwagger() (swagger *openapi3.T, err error) { + resolvePath := PathToRawSpec("") + + loader := openapi3.NewLoader() + loader.IsExternalRefsAllowed = true + loader.ReadFromURIFunc = func(loader *openapi3.Loader, url *url.URL) ([]byte, error) { + pathToFile := url.String() + pathToFile = path.Clean(pathToFile) + getSpec, ok := resolvePath[pathToFile] + if !ok { + err1 := fmt.Errorf("path not found: %s", pathToFile) + return nil, err1 + } + return getSpec() + } + var specData []byte + specData, err = rawSpec() + if err != nil { + return + } + swagger, err = loader.LoadFromData(specData) + if err != nil { + return + } + return +} diff --git a/lib/providers/providers.go b/lib/providers/providers.go new file mode 100644 index 00000000..ea1fd2dc --- /dev/null +++ b/lib/providers/providers.go @@ -0,0 +1,46 @@ +package providers + +import ( + "context" + "log/slog" + "os" + + "github.com/onkernel/hypeman/cmd/api/config" + "github.com/onkernel/hypeman/lib/images" + "github.com/onkernel/hypeman/lib/instances" + "github.com/onkernel/hypeman/lib/logger" + "github.com/onkernel/hypeman/lib/volumes" +) + +// ProvideLogger provides a structured logger +func ProvideLogger() *slog.Logger { + return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ + Level: slog.LevelInfo, + })) +} + +// ProvideContext provides a context with logger attached +func ProvideContext(log *slog.Logger) context.Context { + return logger.AddToContext(context.Background(), log) +} + +// ProvideConfig provides the application configuration +func ProvideConfig() *config.Config { + return config.Load() +} + +// ProvideImageManager provides the image manager +func ProvideImageManager(cfg *config.Config) images.Manager { + return images.NewManager(cfg.DataDir) +} + +// ProvideInstanceManager provides the instance manager +func ProvideInstanceManager(cfg *config.Config) instances.Manager { + return instances.NewManager(cfg.DataDir) +} + +// ProvideVolumeManager provides the volume manager +func ProvideVolumeManager(cfg *config.Config) volumes.Manager { + return volumes.NewManager(cfg.DataDir) +} + diff --git a/lib/volumes/errors.go b/lib/volumes/errors.go new file mode 100644 index 00000000..edac9231 --- /dev/null +++ b/lib/volumes/errors.go @@ -0,0 +1,9 @@ +package volumes + +import "errors" + +var ( + ErrNotFound = errors.New("volume not found") + ErrInUse = errors.New("volume is in use") +) + diff --git a/lib/volumes/manager.go b/lib/volumes/manager.go new file mode 100644 index 00000000..c7d64eec --- /dev/null +++ b/lib/volumes/manager.go @@ -0,0 +1,61 @@ +package volumes + +import ( + "context" + "fmt" + + "github.com/onkernel/hypeman/lib/oapi" +) + +// Manager handles volume lifecycle operations +type Manager interface { + ListVolumes(ctx context.Context) ([]oapi.Volume, error) + CreateVolume(ctx context.Context, req oapi.CreateVolumeRequest) (*oapi.Volume, error) + GetVolume(ctx context.Context, id string) (*oapi.Volume, error) + DeleteVolume(ctx context.Context, id string) error +} + +type manager struct { + dataDir string +} + +// NewManager creates a new volume manager +func NewManager(dataDir string) Manager { + return &manager{ + dataDir: dataDir, + } +} + +func (m *manager) ListVolumes(ctx context.Context) ([]oapi.Volume, error) { + // TODO: implement + return []oapi.Volume{}, nil +} + +func (m *manager) CreateVolume(ctx context.Context, req oapi.CreateVolumeRequest) (*oapi.Volume, error) { + // TODO: implement + return nil, fmt.Errorf("volume creation not yet implemented") +} + +func (m *manager) GetVolume(ctx context.Context, id string) (*oapi.Volume, error) { + // TODO: implement actual logic + exists := false + if !exists { + return nil, ErrNotFound + } + return nil, fmt.Errorf("get volume not yet implemented") +} + +func (m *manager) DeleteVolume(ctx context.Context, id string) error { + // TODO: implement actual logic + exists := false + if !exists { + return ErrNotFound + } + // Check if volume is attached to any instance + inUse := false + if inUse { + return ErrInUse + } + return fmt.Errorf("delete volume not yet implemented") +} + diff --git a/oapi-codegen.yaml b/oapi-codegen.yaml new file mode 100644 index 00000000..17b84b3d --- /dev/null +++ b/oapi-codegen.yaml @@ -0,0 +1,10 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/HEAD/configuration-schema.json +package: oapi +generate: + strict-server: true + client: true + models: true + embedded-spec: true + chi-server: true +output: lib/oapi/oapi.go + diff --git a/openapi.go b/openapi.go new file mode 100644 index 00000000..2c9e0674 --- /dev/null +++ b/openapi.go @@ -0,0 +1,7 @@ +package hypeman + +import _ "embed" + +//go:embed openapi.yaml +var OpenAPIYAML []byte + diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 00000000..37b24c75 --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,959 @@ +openapi: 3.1.0 +info: + title: Hypeman API + description: Generic API for managing VM lifecycle using Cloud Hypervisor with OCI-based workloads + version: 0.1.0 +servers: + - url: http://localhost:8080 + description: Local development server +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + schemas: + ErrorDetail: + type: object + properties: + code: + type: string + description: Lower-level error code providing more specific detail + example: invalid_input + message: + type: string + description: Further detail about the error + example: Image not found + Error: + type: object + required: [code, message] + properties: + code: + type: string + description: Application-specific error code (machine-readable) + example: bad_request + message: + type: string + description: Human-readable error description for debugging + example: "Missing required field: image" + details: + type: array + description: Additional error details (for multiple errors) + items: + $ref: "#/components/schemas/ErrorDetail" + inner_error: + $ref: "#/components/schemas/ErrorDetail" + + InstanceState: + type: string + enum: [Created, Running, Paused, Shutdown, Stopped, Standby] + description: | + Instance state: + - Created: VMM created but not started (Cloud Hypervisor native) + - Running: VM is actively running (Cloud Hypervisor native) + - Paused: VM is paused (Cloud Hypervisor native) + - Shutdown: VM shut down but VMM exists (Cloud Hypervisor native) + - Stopped: No VMM running, no snapshot exists + - Standby: No VMM running, snapshot exists (can be restored) + + VolumeAttachment: + type: object + required: [volume_id, mount_path] + properties: + volume_id: + type: string + description: Volume identifier + example: vol-abc123 + mount_path: + type: string + description: Path where volume is mounted in the guest + example: /mnt/data + readonly: + type: boolean + description: Whether volume is mounted read-only + default: false + + PortMapping: + type: object + required: [host_port, guest_port] + properties: + host_port: + type: integer + description: Port on the host + example: 8080 + guest_port: + type: integer + description: Port in the guest VM + example: 80 + protocol: + type: string + enum: [tcp, udp] + default: tcp + + CreateInstanceRequest: + type: object + required: [id, name, image] + properties: + id: + type: string + description: Unique identifier for the instance (provided by caller) + example: inst-abc123 + name: + type: string + description: Human-readable name + example: my-workload-1 + image: + type: string + description: Image identifier + example: img-chrome-v1 + memory_mb: + type: integer + description: Base memory in MB + default: 1024 + example: 2048 + memory_max_mb: + type: integer + description: Maximum memory with hotplug in MB + default: 4096 + example: 4096 + vcpus: + type: integer + description: Number of virtual CPUs + default: 2 + example: 2 + env: + type: object + additionalProperties: + type: string + description: Environment variables + example: + PORT: "3000" + NODE_ENV: production + timeout_seconds: + type: integer + description: Timeout for scale-to-zero semantics + default: 3600 + example: 7200 + volumes: + type: array + description: Volumes to attach + items: + $ref: "#/components/schemas/VolumeAttachment" + port_mappings: + type: array + description: Port mappings from host to guest + items: + $ref: "#/components/schemas/PortMapping" + + Instance: + type: object + required: [id, name, image, state, created_at] + properties: + id: + type: string + description: Unique identifier + example: inst-abc123 + name: + type: string + description: Human-readable name + example: my-workload-1 + image: + type: string + description: Image identifier + example: img-chrome-v1 + state: + $ref: "#/components/schemas/InstanceState" + fqdn: + type: string + description: Fully qualified domain name + example: inst-abc123.local + nullable: true + private_ip: + type: string + description: Private IP address + example: 192.168.100.10 + nullable: true + memory_mb: + type: integer + description: Configured base memory in MB + example: 2048 + memory_max_mb: + type: integer + description: Configured maximum memory in MB + example: 4096 + vcpus: + type: integer + description: Number of virtual CPUs + example: 2 + env: + type: object + additionalProperties: + type: string + description: Environment variables + timeout_seconds: + type: integer + description: Timeout configuration + example: 7200 + created_at: + type: string + format: date-time + description: Creation timestamp (RFC3339) + example: "2025-01-15T10:30:00Z" + started_at: + type: string + format: date-time + description: Start timestamp (RFC3339) + example: "2025-01-15T10:30:05Z" + nullable: true + stopped_at: + type: string + format: date-time + description: Stop timestamp (RFC3339) + example: "2025-01-15T12:30:00Z" + nullable: true + volumes: + type: array + description: Attached volumes + items: + $ref: "#/components/schemas/VolumeAttachment" + port_mappings: + type: array + description: Port mappings + items: + $ref: "#/components/schemas/PortMapping" + has_snapshot: + type: boolean + description: Whether a snapshot exists for this instance + example: false + + CreateImageRequest: + type: object + required: [name] + properties: + name: + type: string + description: OCI image reference (e.g., docker.io/library/nginx:latest) + example: docker.io/library/nginx:latest + id: + type: string + description: Optional custom identifier (auto-generated if not provided) + example: img-nginx-v1 + + Image: + type: object + required: [id, name, created_at] + properties: + id: + type: string + description: Unique identifier + example: img-nginx-v1 + name: + type: string + description: OCI image reference + example: docker.io/library/nginx:latest + version: + type: string + description: Image tag or digest + example: latest + nullable: true + size_bytes: + type: integer + format: int64 + description: Disk size in bytes + example: 536870912 + entrypoint: + type: array + items: + type: string + description: Entrypoint from container metadata + example: ["/docker-entrypoint.sh"] + nullable: true + cmd: + type: array + items: + type: string + description: CMD from container metadata + example: ["nginx", "-g", "daemon off;"] + nullable: true + env: + type: object + additionalProperties: + type: string + description: Environment variables from container metadata + example: + PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin + working_dir: + type: string + description: Working directory from container metadata + example: /app + nullable: true + created_at: + type: string + format: date-time + description: Creation timestamp (RFC3339) + example: "2025-01-15T10:00:00Z" + + CreateVolumeRequest: + type: object + required: [name, size_gb] + properties: + id: + type: string + description: Optional custom identifier (auto-generated if not provided) + example: vol-data-1 + name: + type: string + description: Volume name + example: my-data-volume + size_gb: + type: integer + description: Size in gigabytes + example: 10 + + Volume: + type: object + required: [id, name, size_gb, created_at] + properties: + id: + type: string + description: Unique identifier + example: vol-data-1 + name: + type: string + description: Volume name + example: my-data-volume + size_gb: + type: integer + description: Size in gigabytes + example: 10 + attached_to: + type: string + description: Instance ID if attached + example: inst-abc123 + nullable: true + mount_path: + type: string + description: Mount path if attached + example: /mnt/data + nullable: true + created_at: + type: string + format: date-time + description: Creation timestamp (RFC3339) + example: "2025-01-15T09:00:00Z" + + AttachVolumeRequest: + type: object + required: [mount_path] + properties: + mount_path: + type: string + description: Path where volume should be mounted + example: /mnt/data + readonly: + type: boolean + description: Mount as read-only + default: false + + Health: + type: object + required: [status] + properties: + status: + type: string + enum: [ok] + example: ok + +paths: + /health: + get: + summary: Health check + operationId: getHealth + responses: + 200: + description: Service is healthy + content: + application/json: + schema: + $ref: "#/components/schemas/Health" + + /images: + get: + summary: List images + operationId: listImages + security: + - bearerAuth: [] + responses: + 200: + description: List of images + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Image" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Pull and convert OCI image + operationId: createImage + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateImageRequest" + responses: + 201: + description: Image created + content: + application/json: + schema: + $ref: "#/components/schemas/Image" + 400: + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /images/{id}: + get: + summary: Get image details + operationId: getImage + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: Image details + content: + application/json: + schema: + $ref: "#/components/schemas/Image" + 404: + description: Image not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + summary: Delete image + operationId: deleteImage + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 204: + description: Image deleted + 404: + description: Image not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /instances: + get: + summary: List instances + operationId: listInstances + security: + - bearerAuth: [] + responses: + 200: + description: List of instances + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Instance" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create and start instance + operationId: createInstance + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateInstanceRequest" + responses: + 201: + description: Instance created + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 400: + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /instances/{id}: + get: + summary: Get instance details + operationId: getInstance + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: Instance details + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 404: + description: Instance not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + summary: Stop and delete instance + operationId: deleteInstance + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 204: + description: Instance deleted + 404: + description: Instance not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /instances/{id}/standby: + post: + summary: Put instance in standby (pause, snapshot, delete VMM) + operationId: standbyInstance + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: Instance in standby + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 404: + description: Instance not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 409: + description: Conflict - instance not in correct state + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /instances/{id}/restore: + post: + summary: Restore instance from standby + operationId: restoreInstance + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: Instance restored + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 404: + description: Instance not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 409: + description: Conflict - instance not in standby or no snapshot + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /instances/{id}/logs: + get: + summary: Stream instance logs (SSE) + operationId: getInstanceLogs + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: follow + in: query + required: false + schema: + type: boolean + default: false + description: Follow logs (stream with SSE) + - name: tail + in: query + required: false + schema: + type: integer + default: 100 + description: Number of lines to return from end + responses: + 200: + description: Log stream + content: + text/event-stream: + schema: + type: string + text/plain: + schema: + type: string + 404: + description: Instance not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /instances/{id}/volumes/{volumeId}: + post: + summary: Attach volume to instance + operationId: attachVolume + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: volumeId + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AttachVolumeRequest" + responses: + 200: + description: Volume attached + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 404: + description: Instance or volume not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 409: + description: Conflict - volume already attached + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + summary: Detach volume from instance + operationId: detachVolume + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: volumeId + in: path + required: true + schema: + type: string + responses: + 200: + description: Volume detached + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 404: + description: Instance or volume not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /volumes: + get: + summary: List volumes + operationId: listVolumes + security: + - bearerAuth: [] + responses: + 200: + description: List of volumes + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Volume" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create volume + operationId: createVolume + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/CreateVolumeRequest" + responses: + 201: + description: Volume created + content: + application/json: + schema: + $ref: "#/components/schemas/Volume" + 400: + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 401: + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /volumes/{id}: + get: + summary: Get volume details + operationId: getVolume + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 200: + description: Volume details + content: + application/json: + schema: + $ref: "#/components/schemas/Volume" + 404: + description: Volume not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + delete: + summary: Delete volume + operationId: deleteVolume + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + 204: + description: Volume deleted + 404: + description: Volume not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 409: + description: Conflict - volume is attached + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + diff --git a/scripts/POC-README.md b/scripts/POC-README.md new file mode 100644 index 00000000..4e2162fd --- /dev/null +++ b/scripts/POC-README.md @@ -0,0 +1,125 @@ +# Cloud Hypervisor POC + +Proof of concept for running 10 Chromium VMs simultaneously using Cloud Hypervisor with disk-based overlays, config disks, networking isolation, and standby/restore functionality. + +## Prerequisites + +Install cloud-hypervisor by [installing the pre-built binaries](https://www.cloudhypervisor.org/docs/prologue/quick-start/#use-pre-built-binaries). Make sure `ch-remote` and `cloud-hypervisor` are in path. + +```bash +ch-remote --version +cloud-hypervisor --version +``` + +Tested with version `v48.0.0` + +Note: Requires `kernel-images-private` cloned to home directory with `iproute2` installed in the Chromium headful image. + +Also, `lsof` and `lz4` needs to be installed on the host + +``` +sudo apt-get install -y lsof lz4 +``` + +## Setup + +Build kernel, initrd, and rootfs with config disk support: + +```bash +./scripts/build-initrd.sh +``` + +This creates: +- `data/system/vmlinux` - Linux kernel +- `data/system/initrd` - BusyBox init with disk-based overlay +- `data/images/chromium-headful/v1/rootfs.ext4` - Chromium rootfs (read-only, shared) + +Configure host network with bridge and guest isolation: + +```bash +./scripts/setup-host-network.sh +``` + +Create 10 VM configurations (IPs 192.168.100.10-19, isolated TAP devices, overlay disks, config disks): + +```bash +./scripts/setup-vms.sh +``` + +## Running VMs + +Start all 10 VMs: + +```bash +./scripts/start-all-vms.sh +``` + +Check VM status: + +```bash +./scripts/list-vms.sh +``` + +View VM logs: + +```bash +./scripts/logs-vm.sh # Show last 100 lines +./scripts/logs-vm.sh -f # Follow logs +``` + +SSH into a VM: + +```bash +./scripts/ssh-vm.sh # Password: root +``` + +Stop a VM: + +```bash +./scripts/stop-vm.sh +./scripts/stop-all-vms.sh # Stop all +``` + +## Standby / Restore + +Standby a VM (pause, snapshot, delete VMM): + +```bash +./scripts/standby-vm.sh +``` + +Restore a VM from snapshot: + +```bash +./scripts/restore-vm.sh +``` + +## Networking + +Enable port forwarding for WebRTC access (localhost:8080-8089 → guest VMs): + +```bash +./scripts/setup-port-forwarding.sh +``` + +Connect to a VM: + +```bash +./scripts/connect-guest.sh +``` + +## Volumes + +Create a persistent volume: + +```bash +./scripts/create-volume.sh +``` + +## Architecture + +- **Disk-based overlay**: Each VM has a 50GB sparse overlay disk on `/dev/vdb` (faster restore than tmpfs) +- **Config disk**: Each VM has a config disk on `/dev/vdc` with VM-specific settings (IP, MAC, envs) +- **Guest isolation**: VMs cannot communicate with each other (iptables + bridge_slave isolation) +- **Serial logging**: All VM output captured to `data/guests/guest-N/logs/console.log` +- **Shared rootfs**: Single read-only rootfs image shared across all VMs