From ca6c4f8d3eb0e986967d6afeed36f8ddc329ee03 Mon Sep 17 00:00:00 2001 From: Jeremy Harrington Date: Sun, 11 Oct 2026 12:55:18 -0700 Subject: [PATCH 1/2] feat: migrate standalone Proxmox VM ownership without database backups --- README.md | 87 ++ cmd/openctl-controller/main.go | 25 +- go.mod | 2 +- internal/cli/ctl.go | 3 + internal/cli/ctl_migration.go | 242 +++ internal/cli/ctl_migration_test.go | 170 +++ internal/controller/manifests/migration.go | 339 +++++ .../controller/manifests/migration_disk.go | 45 + .../controller/manifests/migration_test.go | 134 ++ internal/controller/manifests/store.go | 27 +- .../operations/migration_apply_test.go | 83 + .../controller/operations/migration_test.go | 58 + internal/controller/operations/operations.go | 6 + internal/controller/providers/migration.go | 14 + .../controller/providers/proxmox/migration.go | 74 + .../providers/proxmox/migration_test.go | 80 + internal/controller/server/migration.go | 254 ++++ .../server/migration_concurrency_test.go | 93 ++ internal/controller/server/migration_test.go | 375 +++++ internal/controller/server/resource.go | 31 +- internal/controller/server/server.go | 8 +- .../migrations/0012_resource_releases.sql | 11 + pkg/api/v1/api.pb.go | 1335 +++++++++++------ pkg/api/v1/api.pb.gw.go | 198 +++ pkg/api/v1/api.proto | 71 +- pkg/api/v1/api_grpc.pb.go | 150 +- 26 files changed, 3412 insertions(+), 503 deletions(-) create mode 100644 internal/cli/ctl_migration.go create mode 100644 internal/cli/ctl_migration_test.go create mode 100644 internal/controller/manifests/migration.go create mode 100644 internal/controller/manifests/migration_disk.go create mode 100644 internal/controller/manifests/migration_test.go create mode 100644 internal/controller/operations/migration_apply_test.go create mode 100644 internal/controller/operations/migration_test.go create mode 100644 internal/controller/providers/migration.go create mode 100644 internal/controller/providers/proxmox/migration.go create mode 100644 internal/controller/providers/proxmox/migration_test.go create mode 100644 internal/controller/server/migration.go create mode 100644 internal/controller/server/migration_concurrency_test.go create mode 100644 internal/controller/server/migration_test.go create mode 100644 internal/controller/storage/migrations/0012_resource_releases.sql diff --git a/README.md b/README.md index 6d1790a..7746c0c 100644 --- a/README.md +++ b/README.md @@ -322,6 +322,93 @@ openctl ctl delete VirtualMachine web-01 --api-version proxmox.openctl.io/v1 openctl ctl delete Cluster dev --api-version k3s.openctl.io/v1 ``` +## Move VM Management Between Controllers + +`ctl export`, `ctl adopt`, and `ctl forget` move management of an existing +standalone Proxmox `VirtualMachine` without a SQLite backup or VM recreation. +They are administrator-only operations. Both controllers must run a version +that supports these commands. + +Configure each controller's Proxmox endpoint and credentials independently. +`source-client.yaml` and `destination-client.yaml` below are CLI configurations +pointing at the respective controllers, not VM manifests. The destination +provider context must reach the same VM; its context name can differ. + +Before starting, pause reconciliation on both controllers and stop other clients +from applying, deleting, or running VM actions during the handoff. Disable +manifest GitOps on both controllers and restart them: migration refuses while +the startup-loaded GitOps watcher/pull loop is enabled. Migration also requires +an idle controller, with no pending or running operations. + +Export only the VM being transferred: + +```bash +openctl --config source-client.yaml ctl export VirtualMachine runner-01 \ + --api-version proxmox.openctl.io/v1 -f runner-01.transfer.json +``` + +Export retains source ownership. The versioned JSON bundle contains desired +intent, its digest, a source-controller identifier, and the live Proxmox node, +VMID, and SMBIOS UUID. It does not copy controller credentials, sessions, +operation history, or the database. Manifests can contain literal cloud-init +secrets: export creates a new file with mode `0600`, refuses to overwrite an +existing file, and never prints the bundle to stdout. Transfer it securely and +keep the original unmodified for the source release. + +Adopt at the destination, optionally overriding the provider context: + +```bash +openctl --config destination-client.yaml ctl adopt \ + -f runner-01.transfer.json --provider-context proxmox-home +openctl --config destination-client.yaml ctl get VirtualMachine runner-01 \ + --api-version proxmox.openctl.io/v1 +``` + +Adoption verifies the VM's physical identity and reads its current state before +recording desired intent. It does not apply configuration, resize, start, +delete, or rerun cloud-init. Existing drift remains visible; a subsequent +explicit Apply or resumed reconciliation can converge that drift. A matching +adoption retry is safe; conflicting destination intent is rejected. + +After verifying destination tracking, release only the source controller's +ownership: + +```bash +openctl --config source-client.yaml ctl forget -f runner-01.transfer.json +``` + +Forget validates the original source-controller identifier and full desired +manifest digest, removes desired intent and its mirrored manifest, and leaves +the VM and operation history intact. Changes made since export invalidate the +bundle. Matching retries are safe. A durable release marker blocks stale +Apply, Delete, runtime actions, and GitOps submissions, even after restart; +only explicit adoption can reclaim the resource. There is no distributed +transaction between controllers: keep both paused until this step succeeds. +Update external GitOps repositories to contain only resources each controller +should manage before restarting automation. + +Repeat for each runner being moved. Resources not selected for transfer, such +as a dev VM remaining on the Mac controller, stay managed there. + +To register an existing VM that has a manifest but no source-controller +record, use a single YAML or CUE manifest instead of a bundle: + +```bash +openctl --config destination-client.yaml ctl adopt \ + -f existing-runner.yaml --provider-context proxmox-home +``` + +Manifest adoption matches the existing VM by name and node, rejects ambiguous +matches, and observes it without modifying it. It has no source-controller +receipt and cannot be passed to `ctl forget`; export from the adopting +controller first if that ownership must later be released. + +Migration currently supports only standalone `proxmox.openctl.io/v1` +`VirtualMachine` resources. Composites, owned children, resources with inbound +or outbound references, and resources requiring opaque provider state are +rejected rather than incompletely transferred. Proxmox templates and VMs +without a valid SMBIOS UUID cannot be adopted or exported. + ## Example: Create A Ubuntu VM Save as `vm.yaml`: diff --git a/cmd/openctl-controller/main.go b/cmd/openctl-controller/main.go index 218edcd..44285b9 100644 --- a/cmd/openctl-controller/main.go +++ b/cmd/openctl-controller/main.go @@ -315,6 +315,7 @@ func runServe(args []string) error { // gitOpsWebhook, when the push-triggered reconcile is configured, is built // in the GitOps block below and mounted on the HTTP gateway further down. var gitOpsWebhook *server.GitOpsWebhook + gitOpsEnabled := false // Two-way GitOps: fsnotify watcher on the mirror dir. File edits // become Apply ops tagged source="gitops". Opt-in (config @@ -325,6 +326,7 @@ func runServe(args []string) error { if cfg2, err := config.Load(); err == nil && cfg2 != nil && cfg2.Manifests != nil && cfg2.Manifests.GitOps != nil && cfg2.Manifests.GitOps.Enabled { + gitOpsEnabled = true applyFn := func(ctx context.Context, r *protocol.Resource) error { mJSON, err := json.Marshal(r) if err != nil { @@ -470,17 +472,18 @@ func runServe(args []string) error { sessionStore := auth.NewSessionStore(db) opts := server.Options{ - Listen: *listen, - CertFile: mat.ServerCertPath, - KeyFile: mat.ServerKeyPath, - Registry: registry, - Operations: opStore, - Dispatcher: dispatcher, - Manifests: manifestStore, - Sessions: sessionStore, - DiskMirror: diskMirror, - Repo: gitRepo, - Templates: buildTemplateRegistry(), + Listen: *listen, + CertFile: mat.ServerCertPath, + KeyFile: mat.ServerKeyPath, + Registry: registry, + Operations: opStore, + Dispatcher: dispatcher, + Manifests: manifestStore, + Sessions: sessionStore, + DiskMirror: diskMirror, + Repo: gitRepo, + Templates: buildTemplateRegistry(), + GitOpsEnabled: gitOpsEnabled, } if !*noAuth { opts.Token = token diff --git a/go.mod b/go.mod index 7d5f154..f880fcb 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/coreos/go-oidc/v3 v3.19.0 github.com/fsnotify/fsnotify v1.10.1 github.com/go-jose/go-jose/v4 v4.1.4 + github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-plugin v1.7.0 @@ -28,7 +29,6 @@ require ( github.com/emicklei/proto v1.14.3 // indirect github.com/fatih/color v1.15.0 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/terraform-plugin-log v0.10.0 // indirect github.com/hashicorp/terraform-registry-address v0.4.0 // indirect diff --git a/internal/cli/ctl.go b/internal/cli/ctl.go index c780262..1359a6b 100644 --- a/internal/cli/ctl.go +++ b/internal/cli/ctl.go @@ -34,6 +34,9 @@ and the exec-plugin paths are removed.`, cmd.AddCommand(newCtlGetCommand()) cmd.AddCommand(newCtlDeleteCommand()) cmd.AddCommand(newCtlOpCommand()) + cmd.AddCommand(newCtlExportCommand()) + cmd.AddCommand(newCtlAdoptCommand()) + cmd.AddCommand(newCtlForgetCommand()) return cmd } diff --git a/internal/cli/ctl_migration.go b/internal/cli/ctl_migration.go new file mode 100644 index 0000000..7f7572b --- /dev/null +++ b/internal/cli/ctl_migration.go @@ -0,0 +1,242 @@ +package cli + +import ( + "bytes" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/encoding/protojson" + "gopkg.in/yaml.v3" + + "github.com/openctl/openctl/internal/manifest" + "github.com/openctl/openctl/internal/schema" + apiv1 "github.com/openctl/openctl/pkg/api/v1" +) + +func newCtlExportCommand() *cobra.Command { + var file, apiVersion string + cmd := &cobra.Command{ + Use: "export -f ", + Short: "Export ownership evidence without releasing the resource", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + if file == "" || strings.TrimSpace(apiVersion) == "" { + return fmt.Errorf("--file and --api-version are required") + } + // Reserve the destination before contacting the controller. Never truncate + // an existing bundle, and remove only the file this invocation created. + return writeMigrationExport(file, func() (*apiv1.MigrationBundle, error) { + conn, token, err := dialController() + if err != nil { + return nil, err + } + defer func() { _ = conn.Close() }() + ctx := metadata.AppendToOutgoingContext(cmd.Context(), "authorization", "Bearer "+token) + resp, err := apiv1.NewResourceServiceClient(conn).ExportResource(ctx, &apiv1.ExportResourceRequest{ApiVersion: apiVersion, Kind: args[0], Name: args[1]}) + if err != nil { + return nil, fmt.Errorf("controller export failed (%s): %s", status.Code(err), status.Convert(err).Message()) + } + return resp.GetBundle(), nil + }, func() error { + _, err := fmt.Fprintln(cmd.OutOrStdout(), "Resource exported; source ownership retained.") + return err + }) + }, + } + cmd.Flags().StringVarP(&file, "file", "f", "", "new destination bundle file (mode 0600; never overwritten)") + cmd.Flags().StringVar(&apiVersion, "api-version", "", "resource apiVersion") + return cmd +} + +func newCtlAdoptCommand() *cobra.Command { + var file, providerContext string + cmd := &cobra.Command{ + Use: "adopt -f ", + Short: "Adopt an existing VM without changing it", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + if file == "" { + return fmt.Errorf("--file is required") + } + req, err := loadMigrationAdoption(file) + if err != nil { + return err + } + req.ProviderContext = providerContext + conn, token, err := dialController() + if err != nil { + return err + } + defer func() { _ = conn.Close() }() + ctx := metadata.AppendToOutgoingContext(cmd.Context(), "authorization", "Bearer "+token) + if _, err := apiv1.NewResourceServiceClient(conn).AdoptResource(ctx, req); err != nil { + return fmt.Errorf("controller adoption failed (%s): %s", status.Code(err), status.Convert(err).Message()) + } + _, err = fmt.Fprintln(cmd.OutOrStdout(), "Resource adopted without modifying the VM.") + return err + }, + } + cmd.Flags().StringVarP(&file, "file", "f", "", "migration bundle or single YAML/CUE manifest") + cmd.Flags().StringVar(&providerContext, "provider-context", "", "destination provider context (original bundle remains unchanged)") + return cmd +} + +func newCtlForgetCommand() *cobra.Command { + var file string + cmd := &cobra.Command{ + Use: "forget -f ", + Short: "Release controller ownership without deleting the VM", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + if file == "" { + return fmt.Errorf("--file is required") + } + bundle, err := loadMigrationBundle(file) + if err != nil { + return err + } + conn, token, err := dialController() + if err != nil { + return err + } + defer func() { _ = conn.Close() }() + ctx := metadata.AppendToOutgoingContext(cmd.Context(), "authorization", "Bearer "+token) + if _, err := apiv1.NewResourceServiceClient(conn).ForgetResource(ctx, &apiv1.ForgetResourceRequest{Bundle: bundle}); err != nil { + return fmt.Errorf("controller forget failed (%s): %s", status.Code(err), status.Convert(err).Message()) + } + _, err = fmt.Fprintln(cmd.OutOrStdout(), "Resource ownership released; VM retained.") + return err + }, + } + cmd.Flags().StringVarP(&file, "file", "f", "", "original source migration bundle") + return cmd +} + +func validateMigrationBundle(bundle *apiv1.MigrationBundle) error { + if bundle == nil || bundle.Version != 1 { + return fmt.Errorf("unsupported or missing migration bundle version (expected 1)") + } + r := bundle.Resource + if r == nil || strings.TrimSpace(r.ApiVersion) == "" || strings.TrimSpace(r.Kind) == "" || r.Metadata == nil || strings.TrimSpace(r.Metadata.Name) == "" || r.Spec == nil || len(r.Spec.Fields) == 0 || r.Status != nil { + return fmt.Errorf("migration bundle requires a complete desired resource without status") + } + digest, err := hex.DecodeString(bundle.ManifestDigest) + if strings.TrimSpace(bundle.ControllerId) == "" || strings.TrimSpace(bundle.Identity) == "" || err != nil || len(digest) != 32 { + return fmt.Errorf("migration bundle requires controller ID, identity, and SHA-256 manifest digest") + } + return nil +} + +func loadMigrationBundle(path string) (*apiv1.MigrationBundle, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("cannot read migration bundle: %w", err) + } + bundle := new(apiv1.MigrationBundle) + if err := (protojson.UnmarshalOptions{DiscardUnknown: false}).Unmarshal(data, bundle); err != nil { + // Decoder errors can contain confidential manifest values. + return nil, fmt.Errorf("invalid migration bundle JSON (unknown fields or malformed values)") + } + if err := validateMigrationBundle(bundle); err != nil { + return nil, err + } + return bundle, nil +} + +func loadMigrationAdoption(path string) (*apiv1.AdoptResourceRequest, error) { + if strings.EqualFold(filepath.Ext(path), ".json") { + bundle, err := loadMigrationBundle(path) + if err != nil { + return nil, err + } + return &apiv1.AdoptResourceRequest{Bundle: bundle}, nil + } + if filepath.Ext(path) == ".cue" { + r, err := loadManifest(path) + if err != nil { + return nil, fmt.Errorf("invalid single-resource CUE manifest") + } + if err := schema.Validate(r); err != nil { + return nil, fmt.Errorf("invalid adoption manifest schema") + } + wire, err := resourceToProto(r) + if err != nil { + return nil, fmt.Errorf("invalid adoption manifest values") + } + wire.Status = nil + return &apiv1.AdoptResourceRequest{Resource: wire}, nil + } + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("cannot read adoption manifest: %w", err) + } + // The shared single-document parser ignores trailing YAML documents. Check + // the stream first so adoption cannot silently choose one of several VMs. + decoder := yaml.NewDecoder(bytes.NewReader(data)) + var document yaml.Node + if err := decoder.Decode(&document); err != nil { + return nil, fmt.Errorf("invalid single-resource YAML manifest") + } + var extra yaml.Node + if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) { + return nil, fmt.Errorf("adoption requires exactly one YAML document") + } + r, err := manifest.Parse(data) + if err != nil { + return nil, fmt.Errorf("invalid adoption manifest") + } + if err := schema.Validate(r); err != nil { + return nil, fmt.Errorf("invalid adoption manifest schema") + } + wire, err := resourceToProto(r) + if err != nil { + return nil, fmt.Errorf("invalid adoption manifest values") + } + wire.Status = nil + return &apiv1.AdoptResourceRequest{Resource: wire}, nil +} + +func writeMigrationExport(path string, export func() (*apiv1.MigrationBundle, error), completed func() error) (err error) { + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0600) + if err != nil { + return fmt.Errorf("cannot create export file: %w", err) + } + committed := false + defer func() { + _ = file.Close() + if !committed { + _ = os.Remove(path) + } + }() + bundle, err := export() + if err != nil { + return err + } + if err := validateMigrationBundle(bundle); err != nil { + return err + } + data, err := (protojson.MarshalOptions{Indent: " "}).Marshal(bundle) + if err != nil { + return fmt.Errorf("cannot encode migration bundle") + } + data = append(data, '\n') + if _, err := file.Write(data); err != nil { + return fmt.Errorf("cannot write export file: %w", err) + } + if err := file.Sync(); err != nil { + return fmt.Errorf("cannot sync export file: %w", err) + } + if err := file.Close(); err != nil { + return fmt.Errorf("cannot close export file: %w", err) + } + committed = true + return completed() +} diff --git a/internal/cli/ctl_migration_test.go b/internal/cli/ctl_migration_test.go new file mode 100644 index 0000000..da42f37 --- /dev/null +++ b/internal/cli/ctl_migration_test.go @@ -0,0 +1,170 @@ +package cli + +import ( + "errors" + "os" + "path/filepath" + "strings" + "testing" + + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" + + apiv1 "github.com/openctl/openctl/pkg/api/v1" +) + +func migrationTestBundle(t *testing.T) *apiv1.MigrationBundle { + t.Helper() + spec, err := structpb.NewStruct(map[string]any{"context": "source", "node": "pve1", "vmid": 123.0, "template": map[string]any{"name": "ubuntu"}, "cloudInit": map[string]any{"password": "confidential-password"}}) + if err != nil { + t.Fatal(err) + } + return &apiv1.MigrationBundle{Version: 1, ControllerId: "source-controller", ManifestDigest: strings.Repeat("a", 64), Identity: "vm-identity", Resource: &apiv1.Resource{ApiVersion: "proxmox.openctl.io/v1", Kind: "VirtualMachine", Metadata: &apiv1.Metadata{Name: "existing", Annotations: map[string]string{"owner": "original"}}, Spec: spec}} +} + +func TestMigrationExportExclusivePrivateRoundTrip(t *testing.T) { + path := filepath.Join(t.TempDir(), "bundle.json") + bundle := migrationTestBundle(t) + completed := false + if err := writeMigrationExport(path, func() (*apiv1.MigrationBundle, error) { return bundle, nil }, func() error { completed = true; return nil }); err != nil { + t.Fatal(err) + } + if !completed { + t.Fatal("completion not reported") + } + info, err := os.Stat(path) + if err != nil { + t.Fatal(err) + } + if info.Mode().Perm() != 0600 { + t.Fatalf("permissions = %o", info.Mode().Perm()) + } + loaded, err := loadMigrationBundle(path) + if err != nil { + t.Fatal(err) + } + if !proto.Equal(bundle, loaded) { + t.Fatal("bundle lost desired intent or ownership evidence") + } + before, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + called := false + if err := writeMigrationExport(path, func() (*apiv1.MigrationBundle, error) { called = true; return nil, nil }, func() error { t.Fatal("unexpected completion"); return nil }); err == nil { + t.Fatal("overwrote existing bundle") + } + if called { + t.Fatal("contacted controller before detecting existing destination") + } + after, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if string(before) != string(after) { + t.Fatal("existing bundle changed") + } +} + +func TestMigrationExportFailureRemovesOwnedFile(t *testing.T) { + for _, invalid := range []bool{false, true} { + path := filepath.Join(t.TempDir(), "failed.json") + err := writeMigrationExport(path, func() (*apiv1.MigrationBundle, error) { + if invalid { + return &apiv1.MigrationBundle{Version: 9}, nil + } + return nil, errors.New("unavailable") + }, func() error { t.Fatal("completion after failure"); return nil }) + if err == nil { + t.Fatal("expected failure") + } + if _, err := os.Stat(path); !os.IsNotExist(err) { + t.Fatalf("partial export retained: %v", err) + } + } +} + +func TestMigrationBundleStrictValidation(t *testing.T) { + valid := migrationTestBundle(t) + data, err := protojson.Marshal(valid) + if err != nil { + t.Fatal(err) + } + cases := map[string]string{ + "unknown": strings.TrimSuffix(string(data), "}") + `,"secretUnknown":"confidential-password"}`, + "nested unknown": strings.Replace(string(data), `"metadata":`, `"metadata":{"confidential-password":"hidden"},"replacedMetadata":`, 1), + "trailing": string(data) + `{}`, + "malformed": `{"version":"confidential-password"}`, + } + for name, mutate := range map[string]func(*apiv1.MigrationBundle){ + "version": func(b *apiv1.MigrationBundle) { b.Version = 2 }, + "controller": func(b *apiv1.MigrationBundle) { b.ControllerId = "" }, + "digest": func(b *apiv1.MigrationBundle) { b.ManifestDigest = "bad" }, + "identity": func(b *apiv1.MigrationBundle) { b.Identity = "" }, + "resource": func(b *apiv1.MigrationBundle) { b.Resource = nil }, + "metadata": func(b *apiv1.MigrationBundle) { b.Resource.Metadata = nil }, + "spec": func(b *apiv1.MigrationBundle) { b.Resource.Spec = nil }, + "status": func(b *apiv1.MigrationBundle) { b.Resource.Status = &structpb.Struct{} }, + } { + b := proto.Clone(valid).(*apiv1.MigrationBundle) + mutate(b) + encoded, err := protojson.Marshal(b) + if err != nil { + t.Fatal(err) + } + cases[name] = string(encoded) + } + for name, content := range cases { + t.Run(name, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "bundle.json") + writeFile(t, path, content) + if _, err := loadMigrationBundle(path); err == nil { + t.Fatal("invalid bundle accepted") + } else if strings.Contains(err.Error(), "confidential-password") { + t.Fatal("error leaked secret") + } + }) + } +} + +func TestMigrationAdoptionSingleYAMLAndCUE(t *testing.T) { + yaml := "apiVersion: proxmox.openctl.io/v1\nkind: VirtualMachine\nmetadata:\n name: existing\nspec:\n node: pve1\n template:\n name: ubuntu\n cpu:\n cores: 2\n memory:\n size: 4096\n" + cue := `apiVersion: "proxmox.openctl.io/v1" +kind: "VirtualMachine" +metadata: name: "existing" +spec: { + node: "pve1" + template: name: "ubuntu" + cpu: cores: 2 + memory: size: 4096 +} +` + for ext, content := range map[string]string{".yaml": yaml, ".cue": cue} { + t.Run(ext, func(t *testing.T) { + path := filepath.Join(t.TempDir(), "existing"+ext) + writeFile(t, path, content) + req, err := loadMigrationAdoption(path) + if err != nil { + t.Fatal(err) + } + if req.Bundle != nil || req.Resource == nil || req.Resource.Metadata.Name != "existing" || req.Resource.Spec.Fields["cpu"].GetStructValue().Fields["cores"].GetNumberValue() != 2 { + t.Fatal("manifest intent not preserved") + } + if _, err := loadMigrationBundle(path); err == nil { + t.Fatal("forget accepted manifest without source evidence") + } + }) + } + path := filepath.Join(t.TempDir(), "multiple.yaml") + writeFile(t, path, yaml+"---\n"+yaml) + if _, err := loadMigrationAdoption(path); err == nil { + t.Fatal("multiple manifests silently accepted") + } + writeFile(t, path, "apiVersion: proxmox.openctl.io/v1\nkind: VirtualMachine\nmetadata:\n name: bad\nspec:\n cpu:\n cores: confidential-password\n") + if _, err := loadMigrationAdoption(path); err == nil { + t.Fatal("invalid schema accepted") + } else if strings.Contains(err.Error(), "confidential-password") { + t.Fatal("validation leaked secret") + } +} diff --git a/internal/controller/manifests/migration.go b/internal/controller/manifests/migration.go new file mode 100644 index 0000000..de96be6 --- /dev/null +++ b/internal/controller/manifests/migration.go @@ -0,0 +1,339 @@ +package manifests + +import ( + "context" + "crypto/rand" + "crypto/sha256" + "database/sql" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/openctl/openctl/internal/controller/refs" + "github.com/openctl/openctl/pkg/protocol" +) + +var ( + ErrMigrationBusy = errors.New("controller has pending or running operations; wait for them to finish before migration") + ErrMigrationConflict = errors.New("migration conflicts with current controller intent") + ErrMigrationUnsupported = errors.New("migration requires a standalone Proxmox VirtualMachine without provider state or resource references") + ErrMigrationNotFound = errors.New("resource has no managed desired manifest") +) + +// ForgottenError fences all ordinary submissions after ownership is released. +// Explicit adoption is the only operation that clears the fence. +type ForgottenError struct { + APIVersion string + Kind string + Name string +} + +func (e *ForgottenError) Error() string { + return fmt.Sprintf("%s %q was forgotten by this controller; use ctl adopt to reclaim management", e.Kind, e.Name) +} + +type rowQuerier interface { + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +// CheckNotForgotten works with a DB or transaction. Call inside the operation +// admission transaction to serialize release against new queued work. +func CheckNotForgotten(ctx context.Context, q rowQuerier, av, kind, name string) error { + var released bool + if err := q.QueryRowContext(ctx, `SELECT EXISTS(SELECT 1 FROM resource_releases WHERE api_version = ? AND kind = ? AND name = ?)`, av, kind, name).Scan(&released); err != nil { + return fmt.Errorf("check resource release: %w", err) + } + if released { + return &ForgottenError{APIVersion: av, Kind: kind, Name: name} + } + return nil +} + +func (s *Store) IsForgotten(ctx context.Context, av, kind, name string) (bool, error) { + err := CheckNotForgotten(ctx, s.db, av, kind, name) + var forgotten *ForgottenError + if errors.As(err, &forgotten) { + return true, nil + } + return false, err +} + +// MigrationDigest covers the entire desired manifest, including annotations. +// Unlike the apply cache hash, changing automation/ownership annotations must +// invalidate an exported handoff. Observed status is not desired intent. +func MigrationDigest(r *protocol.Resource) (string, error) { + if r == nil { + return "", fmt.Errorf("migration manifest is required") + } + desired := *r + desired.Status = nil + data, err := json.Marshal(&desired) + if err != nil { + return "", fmt.Errorf("encode migration manifest: %w", err) + } + sum := sha256.Sum256(data) + return hex.EncodeToString(sum[:]), nil +} + +type MigrationSnapshot struct { + ControllerID string + Digest string + Resource *protocol.Resource +} + +// ExportMigration snapshots only one resource's desired intent. No operations, +// authentication material, or unrelated resources leave the controller. +func (s *Store) ExportMigration(ctx context.Context, av, kind, name string) (*MigrationSnapshot, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + if err := migrationIdle(ctx, tx); err != nil { + return nil, err + } + if err := CheckNotForgotten(ctx, tx, av, kind, name); err != nil { + return nil, err + } + r, err := loadMigrationManifest(ctx, tx, av, kind, name) + if err != nil { + return nil, err + } + if r == nil { + return nil, ErrMigrationNotFound + } + if err := migrationStandalone(ctx, tx, r); err != nil { + return nil, err + } + id, err := migrationControllerID(ctx, tx) + if err != nil { + return nil, err + } + digest, err := MigrationDigest(r) + if err != nil { + return nil, err + } + if err := tx.Commit(); err != nil { + return nil, err + } + return &MigrationSnapshot{ControllerID: id, Digest: digest, Resource: r}, nil +} + +// Adopt records desired intent without applying it. The RPC verifies the live +// provider identity before this transaction; this method never calls a provider. +func (s *Store) Adopt(ctx context.Context, r *protocol.Resource) error { + if err := validateMigrationResource(r); err != nil { + return err + } + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if err := migrationIdle(ctx, tx); err != nil { + return err + } + if err := migrationStandalone(ctx, tx, r); err != nil { + return err + } + old, err := loadMigrationManifest(ctx, tx, r.APIVersion, r.Kind, r.Metadata.Name) + if err != nil { + return err + } + if old != nil { + oldDigest, err := MigrationDigest(old) + if err != nil { + return err + } + newDigest, err := MigrationDigest(r) + if err != nil { + return err + } + if oldDigest != newDigest { + return fmt.Errorf("%w: destination already manages a different manifest for %s %q", ErrMigrationConflict, r.Kind, r.Metadata.Name) + } + } + if _, err := tx.ExecContext(ctx, `DELETE FROM resource_releases WHERE api_version = ? AND kind = ? AND name = ?`, r.APIVersion, r.Kind, r.Metadata.Name); err != nil { + return err + } + if err := saveManifest(ctx, tx, r, "", SourceCLI); err != nil { + return err + } + return tx.Commit() +} + +// Forget atomically removes only desired intent and installs a persistent +// release fence. History is retained. A stale/wrong-controller bundle cannot +// relinquish newer intent. The same successful release is retryable. +func (s *Store) Forget(ctx context.Context, r *protocol.Resource, controllerID, digest string) error { + if err := validateMigrationResource(r); err != nil { + return err + } + actualDigest, err := MigrationDigest(r) + if err != nil { + return err + } + if controllerID == "" || digest == "" || digest != actualDigest { + return fmt.Errorf("%w: invalid bundle manifest digest or source controller", ErrMigrationConflict) + } + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if err := migrationIdle(ctx, tx); err != nil { + return err + } + id, err := migrationControllerID(ctx, tx) + if err != nil { + return err + } + if id != controllerID { + return fmt.Errorf("%w: bundle belongs to another source controller", ErrMigrationConflict) + } + old, err := loadMigrationManifest(ctx, tx, r.APIVersion, r.Kind, r.Metadata.Name) + if err != nil { + return err + } + if old == nil { + var prior string + err := tx.QueryRowContext(ctx, `SELECT manifest_digest FROM resource_releases WHERE api_version = ? AND kind = ? AND name = ?`, r.APIVersion, r.Kind, r.Metadata.Name).Scan(&prior) + if errors.Is(err, sql.ErrNoRows) { + return ErrMigrationNotFound + } + if err != nil { + return err + } + if prior != digest { + return fmt.Errorf("%w: release belongs to a different export", ErrMigrationConflict) + } + return tx.Commit() + } + currentDigest, err := MigrationDigest(old) + if err != nil { + return err + } + if currentDigest != digest { + return fmt.Errorf("%w: source manifest changed after export; export it again", ErrMigrationConflict) + } + if err := migrationStandalone(ctx, tx, old); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, `INSERT INTO resource_releases (api_version, kind, name, manifest_digest, released_at) VALUES (?, ?, ?, ?, ?) ON CONFLICT(api_version, kind, name) DO UPDATE SET manifest_digest = excluded.manifest_digest, released_at = excluded.released_at`, r.APIVersion, r.Kind, r.Metadata.Name, digest, time.Now().UTC().Format(time.RFC3339Nano)); err != nil { + return err + } + if _, err := tx.ExecContext(ctx, `DELETE FROM applied_manifests WHERE api_version = ? AND kind = ? AND name = ?`, r.APIVersion, r.Kind, r.Metadata.Name); err != nil { + return err + } + return tx.Commit() +} + +func validateMigrationResource(r *protocol.Resource) error { + if r == nil || r.APIVersion != "proxmox.openctl.io/v1" || r.Kind != "VirtualMachine" || r.Metadata.Name == "" { + return ErrMigrationUnsupported + } + if r.Metadata.Labels["openctl.io/owner-kind"] != "" || r.Metadata.Labels["openctl.io/owner-name"] != "" || r.Metadata.Labels["openctl.io/cluster"] != "" || len(refs.Collect(r.Spec)) != 0 { + return ErrMigrationUnsupported + } + return nil +} + +func migrationIdle(ctx context.Context, tx *sql.Tx) error { + var busy bool + if err := tx.QueryRowContext(ctx, `SELECT EXISTS(SELECT 1 FROM operations WHERE status IN ('pending', 'running'))`).Scan(&busy); err != nil { + return err + } + if busy { + return ErrMigrationBusy + } + return nil +} + +func migrationStandalone(ctx context.Context, tx *sql.Tx, r *protocol.Resource) error { + if err := validateMigrationResource(r); err != nil { + return err + } + var stateful bool + if err := tx.QueryRowContext(ctx, `SELECT EXISTS(SELECT 1 FROM provider_state WHERE api_version = ? AND kind = ? AND name = ?)`, r.APIVersion, r.Kind, r.Metadata.Name).Scan(&stateful); err != nil { + return err + } + if stateful { + return fmt.Errorf("%w: opaque provider state is present", ErrMigrationUnsupported) + } + rows, err := tx.QueryContext(ctx, `SELECT api_version, kind, name, spec_json, COALESCE(metadata_json, '') FROM applied_manifests`) + if err != nil { + return err + } + defer func() { _ = rows.Close() }() + for rows.Next() { + var av, kind, name, specJSON, metaJSON string + if err := rows.Scan(&av, &kind, &name, &specJSON, &metaJSON); err != nil { + return err + } + if av == r.APIVersion && kind == r.Kind && name == r.Metadata.Name { + continue + } + var spec map[string]any + if err := json.Unmarshal([]byte(specJSON), &spec); err != nil { + return fmt.Errorf("read managed resource references: %w", err) + } + for _, ref := range refs.Collect(spec) { + if ref.APIVersion == r.APIVersion && ref.Kind == r.Kind && ref.Name == r.Metadata.Name { + return fmt.Errorf("%w: %s %q references this VM", ErrMigrationUnsupported, kind, name) + } + } + if metaJSON != "" { + var meta protocol.ResourceMetadata + if err := json.Unmarshal([]byte(metaJSON), &meta); err != nil { + return fmt.Errorf("read managed ownership: %w", err) + } + if meta.Labels["openctl.io/owner-kind"] == r.Kind && meta.Labels["openctl.io/owner-name"] == r.Metadata.Name { + return fmt.Errorf("%w: %s %q is owned by this VM", ErrMigrationUnsupported, kind, name) + } + } + } + return rows.Err() +} + +func loadMigrationManifest(ctx context.Context, tx *sql.Tx, av, kind, name string) (*protocol.Resource, error) { + var specJSON, metaJSON string + err := tx.QueryRowContext(ctx, `SELECT spec_json, COALESCE(metadata_json, '') FROM applied_manifests WHERE api_version = ? AND kind = ? AND name = ?`, av, kind, name).Scan(&specJSON, &metaJSON) + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + if err != nil { + return nil, err + } + r := &protocol.Resource{APIVersion: av, Kind: kind, Metadata: protocol.ResourceMetadata{Name: name}} + if err := json.Unmarshal([]byte(specJSON), &r.Spec); err != nil { + return nil, fmt.Errorf("decode desired spec: %w", err) + } + if metaJSON != "" { + if err := json.Unmarshal([]byte(metaJSON), &r.Metadata); err != nil { + return nil, fmt.Errorf("decode desired metadata: %w", err) + } + } + return r, nil +} + +func migrationControllerID(ctx context.Context, tx *sql.Tx) (string, error) { + var id string + err := tx.QueryRowContext(ctx, `SELECT value FROM controller_meta WHERE key = 'migration_controller_id'`).Scan(&id) + if err == nil { + return id, nil + } + if !errors.Is(err, sql.ErrNoRows) { + return "", err + } + var raw [16]byte + if _, err := rand.Read(raw[:]); err != nil { + return "", err + } + id = hex.EncodeToString(raw[:]) + if _, err := tx.ExecContext(ctx, `INSERT INTO controller_meta (key, value) VALUES ('migration_controller_id', ?)`, id); err != nil { + return "", err + } + return id, nil +} diff --git a/internal/controller/manifests/migration_disk.go b/internal/controller/manifests/migration_disk.go new file mode 100644 index 0000000..b735316 --- /dev/null +++ b/internal/controller/manifests/migration_disk.go @@ -0,0 +1,45 @@ +package manifests + +import ( + "context" + "fmt" + + "github.com/openctl/openctl/pkg/protocol" +) + +// Materialize projects committed adoption intent without performing another +// database save. Migration RPCs require GitOps watching to be disabled. +func (m *DiskMirror) Materialize(ctx context.Context, r *protocol.Resource) error { + if r == nil { + return fmt.Errorf("adoption manifest is required") + } + if err := CheckNotForgotten(ctx, m.store.db, r.APIVersion, r.Kind, r.Metadata.Name); err != nil { + return err + } + if err := m.writeFile(r); err != nil { + return fmt.Errorf("materialize adopted manifest: %w", err) + } + if m.hook != nil { + return m.hook(ctx, r.Kind, r, "adopt") + } + return nil +} + +// RemoveProjection removes only the mirror after the canonical release is +// committed. The persistent release marker fences future Git pulls/submissions. +func (m *DiskMirror) RemoveProjection(ctx context.Context, av, kind, name string) error { + forgotten, err := m.store.IsForgotten(ctx, av, kind, name) + if err != nil { + return err + } + if !forgotten { + return fmt.Errorf("%w: projection removal requires committed ownership release", ErrMigrationConflict) + } + if err := m.removeFile(av, kind, name); err != nil { + return fmt.Errorf("remove forgotten manifest projection: %w", err) + } + if m.hook != nil { + return m.hook(ctx, kind, &protocol.Resource{APIVersion: av, Kind: kind, Metadata: protocol.ResourceMetadata{Name: name}}, "forget") + } + return nil +} diff --git a/internal/controller/manifests/migration_test.go b/internal/controller/manifests/migration_test.go new file mode 100644 index 0000000..3cef37a --- /dev/null +++ b/internal/controller/manifests/migration_test.go @@ -0,0 +1,134 @@ +package manifests + +import ( + "context" + "errors" + "path/filepath" + "testing" + + "github.com/openctl/openctl/internal/controller/storage" + "github.com/openctl/openctl/pkg/protocol" +) + +func TestMigrationReleaseSurvivesRestartAndExplicitAdoption(t *testing.T) { + ctx := context.Background() + path := filepath.Join(t.TempDir(), "controller.db") + db, err := storage.Open(ctx, path) + if err != nil { + t.Fatal(err) + } + s := New(db) + vm := sampleVM(4) + other := sampleVM(8) + other.Metadata.Name = "dev-system" + if err := s.Save(ctx, vm); err != nil { + t.Fatal(err) + } + if err := s.Save(ctx, other); err != nil { + t.Fatal(err) + } + snapshot, err := s.ExportMigration(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name) + if err != nil { + t.Fatal(err) + } + if err := s.Forget(ctx, snapshot.Resource, snapshot.ControllerID, snapshot.Digest); err != nil { + t.Fatal(err) + } + if err := db.Close(); err != nil { + t.Fatal(err) + } + db, err = storage.Open(ctx, path) + if err != nil { + t.Fatal(err) + } + defer func() { _ = db.Close() }() + s = New(db) + var forgotten *ForgottenError + if err := s.Save(ctx, vm); !errors.As(err, &forgotten) { + t.Fatalf("stale desired intent was not fenced after restart: %v", err) + } + if got, err := s.Load(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name); err != nil || got != nil { + t.Fatalf("forgotten intent reappeared: %v, %v", got, err) + } + if got, err := s.Load(ctx, other.APIVersion, other.Kind, other.Metadata.Name); err != nil || got == nil || got.Spec["cpus"] != float64(8) { + t.Fatalf("unrelated dev-system changed: %v, %v", got, err) + } + if err := s.Forget(ctx, snapshot.Resource, snapshot.ControllerID, snapshot.Digest); err != nil { + t.Fatalf("same release could not be retried: %v", err) + } + if err := s.Adopt(ctx, snapshot.Resource); err != nil { + t.Fatal(err) + } + if released, err := s.IsForgotten(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name); err != nil || released { + t.Fatalf("explicit adoption did not clear release: %v, %v", released, err) + } + if got, err := s.Load(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name); err != nil || got == nil || got.Spec["cpus"] != float64(4) { + t.Fatalf("adopted intent differs: %v, %v", got, err) + } +} + +func TestMigrationAnnotationChangeInvalidatesExport(t *testing.T) { + s := newTestStore(t) + ctx := context.Background() + vm := sampleVM(4) + vm.Metadata.Annotations = map[string]string{"openctl.io/autoReconcile": "false"} + if err := s.Save(ctx, vm); err != nil { + t.Fatal(err) + } + exported, err := s.ExportMigration(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name) + if err != nil { + t.Fatal(err) + } + vm.Metadata.Annotations["openctl.io/autoReconcile"] = "true" + if err := s.Save(ctx, vm); err != nil { + t.Fatal(err) + } + if err := s.Forget(ctx, exported.Resource, exported.ControllerID, exported.Digest); !errors.Is(err, ErrMigrationConflict) { + t.Fatalf("stale annotation change allowed ownership release: %v", err) + } + got, err := s.Load(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name) + if err != nil || got == nil || got.Metadata.Annotations["openctl.io/autoReconcile"] != "true" { + t.Fatalf("newer automation intent was lost: %v, %v", got, err) + } +} + +func TestMigrationRejectsReferencesAndOpaqueState(t *testing.T) { + for _, dependency := range []string{"inbound-reference", "outbound-reference", "owned-child", "provider-state"} { + t.Run(dependency, func(t *testing.T) { + s := newTestStore(t) + ctx := context.Background() + vm := sampleVM(4) + if dependency == "outbound-reference" { + vm.Spec["dependency"] = map[string]any{"$ref": map[string]any{"apiVersion": "other.io/v1", "kind": "Other", "name": "other"}} + } + if err := s.Save(ctx, vm); err != nil { + t.Fatal(err) + } + switch dependency { + case "inbound-reference", "owned-child": + child := &protocol.Resource{APIVersion: "other.io/v1", Kind: "Other", Metadata: protocol.ResourceMetadata{Name: "dependent"}, Spec: map[string]any{}} + if dependency == "inbound-reference" { + child.Spec["vm"] = map[string]any{"$ref": map[string]any{"apiVersion": vm.APIVersion, "kind": vm.Kind, "name": vm.Metadata.Name, "field": "status.ip"}} + } else { + child.Metadata.Labels = map[string]string{"openctl.io/owner-kind": vm.Kind, "openctl.io/owner-name": vm.Metadata.Name} + } + if err := s.Save(ctx, child); err != nil { + t.Fatal(err) + } + case "provider-state": + if _, err := s.db.ExecContext(ctx, `INSERT INTO provider_state (api_version, kind, name, state_blob, schema_version, updated_at) VALUES (?, ?, ?, ?, 1, 'now')`, vm.APIVersion, vm.Kind, vm.Metadata.Name, []byte("opaque-identity")); err != nil { + t.Fatal(err) + } + } + if _, err := s.ExportMigration(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name); !errors.Is(err, ErrMigrationUnsupported) { + t.Fatalf("unsafe %s exported: %v", dependency, err) + } + if err := s.Adopt(ctx, vm); !errors.Is(err, ErrMigrationUnsupported) { + t.Fatalf("unsafe %s adopted: %v", dependency, err) + } + if got, err := s.Load(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name); err != nil || got == nil || got.Spec["cpus"] != float64(4) { + t.Fatalf("refusal altered original intent: %v, %v", got, err) + } + }) + } +} diff --git a/internal/controller/manifests/store.go b/internal/controller/manifests/store.go index 59be006..07709a1 100644 --- a/internal/controller/manifests/store.go +++ b/internal/controller/manifests/store.go @@ -58,6 +58,25 @@ func (s *Store) SaveWithRefsHash(ctx context.Context, r *protocol.Resource, refs if r == nil || r.APIVersion == "" || r.Kind == "" || r.Metadata.Name == "" { return fmt.Errorf("save: apiVersion, kind, metadata.name all required") } + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer func() { _ = tx.Rollback() }() + if err := CheckNotForgotten(ctx, tx, r.APIVersion, r.Kind, r.Metadata.Name); err != nil { + return err + } + if err := saveManifest(ctx, tx, r, refsHash, SourceFromContext(ctx)); err != nil { + return err + } + return tx.Commit() +} + +type manifestExecer interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) +} + +func saveManifest(ctx context.Context, q manifestExecer, r *protocol.Resource, refsHash, source string) error { specJSON, err := json.Marshal(r.Spec) if err != nil { return fmt.Errorf("encode spec: %w", err) @@ -66,17 +85,11 @@ func (s *Store) SaveWithRefsHash(ctx context.Context, r *protocol.Resource, refs if err != nil { return fmt.Errorf("encode metadata: %w", err) } - // Provenance (K3): the dispatcher attaches the op's source to ctx via - // WithSource before calling the sink, so the row records who applied it - // ("cli"/"ui"/"gitops"/"auto-reconcile") as a first-class field. Empty - // when no source was attached — read back as "provenance unknown", which - // the Pruner treats like the old GC'd-op case (not protected). - source := SourceFromContext(ctx) // SQLite treats "" as NULL-adjacent for this workflow — we store // the empty string when no refs_hash is supplied, and read it back // via COALESCE. This keeps the schema uniform without requiring // callers to plumb sql.NullString values. - _, err = s.db.ExecContext(ctx, + _, err = q.ExecContext(ctx, `INSERT INTO applied_manifests (api_version, kind, name, spec_json, metadata_json, applied_at, input_hash, refs_hash, source) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(api_version, kind, name) DO UPDATE SET diff --git a/internal/controller/operations/migration_apply_test.go b/internal/controller/operations/migration_apply_test.go new file mode 100644 index 0000000..37f68ea --- /dev/null +++ b/internal/controller/operations/migration_apply_test.go @@ -0,0 +1,83 @@ +package operations + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "path/filepath" + "strconv" + "sync/atomic" + "testing" + + "github.com/openctl/openctl/internal/controller/manifests" + "github.com/openctl/openctl/internal/controller/providers" + pmprovider "github.com/openctl/openctl/internal/controller/providers/proxmox" + "github.com/openctl/openctl/internal/controller/storage" + "github.com/openctl/openctl/pkg/protocol" +) + +func TestExplicitApplyConvergesAfterReadOnlyAdoption(t *testing.T) { + ctx := context.Background() + var liveCPU atomic.Int64 + liveCPU.Store(2) + pve := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/api2/json/nodes": + _ = json.NewEncoder(w).Encode(map[string]any{"data": []map[string]any{{"node": "pve1"}}}) + case "/api2/json/nodes/pve1/qemu": + _ = json.NewEncoder(w).Encode(map[string]any{"data": []map[string]any{{"name": "runner", "vmid": 101, "status": "stopped"}}}) + case "/api2/json/nodes/pve1/qemu/101/config": + if r.Method == http.MethodPut { + if err := r.ParseForm(); err != nil { + t.Error(err) + http.Error(w, "form", 400) + return + } + cores, err := strconv.ParseInt(r.Form.Get("cores"), 10, 64) + if err != nil { + t.Error(err) + http.Error(w, "cores", 400) + return + } + liveCPU.Store(cores) + _ = json.NewEncoder(w).Encode(map[string]any{"data": nil}) + return + } + _ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"name": "runner", "cores": liveCPU.Load(), "sockets": 1, "memory": "2048"}}) + default: + http.NotFound(w, r) + } + })) + defer pve.Close() + db, err := storage.Open(ctx, filepath.Join(t.TempDir(), "controller.db")) + if err != nil { + t.Fatal(err) + } + defer func() { _ = db.Close() }() + intent := manifests.New(db) + vm := &protocol.Resource{APIVersion: "proxmox.openctl.io/v1", Kind: "VirtualMachine", Metadata: protocol.ResourceMetadata{Name: "runner"}, Spec: map[string]any{"node": "pve1", "cpu": map[string]any{"cores": 4}, "memory": map[string]any{"size": 2048}}} + if err := intent.Adopt(ctx, vm); err != nil { + t.Fatal(err) + } + if liveCPU.Load() != 2 { + t.Fatal("adoption changed live hardware") + } + reg := providers.NewRegistry() + reg.Register(pmprovider.New(&pmprovider.Config{Endpoint: pve.URL, TokenID: "fixture", TokenSecret: "fixture"})) + ops := New(db, 0) + dispatcher := NewDispatcher(ops, reg, intent, 0) + manifest, err := json.Marshal(vm) + if err != nil { + t.Fatal(err) + } + op, err := ops.Submit(ctx, &Operation{Type: TypeApply, APIVersion: vm.APIVersion, Kind: vm.Kind, ResourceName: vm.Metadata.Name, ManifestJSON: string(manifest)}) + if err != nil { + t.Fatal(err) + } + runPendingOp(t, dispatcher, ops, op.ID, StatusSucceeded) + if liveCPU.Load() != 4 { + t.Fatalf("explicit apply incorrectly treated adopted desired state as already converged: live CPU = %d, want 4", liveCPU.Load()) + } +} diff --git a/internal/controller/operations/migration_test.go b/internal/controller/operations/migration_test.go new file mode 100644 index 0000000..ddb79b3 --- /dev/null +++ b/internal/controller/operations/migration_test.go @@ -0,0 +1,58 @@ +package operations + +import ( + "context" + "errors" + "path/filepath" + "testing" + + "github.com/openctl/openctl/internal/controller/manifests" + "github.com/openctl/openctl/internal/controller/storage" + "github.com/openctl/openctl/pkg/protocol" +) + +func TestReleasedResourceCannotBeQueuedByGitOps(t *testing.T) { + ctx := context.Background() + db, err := storage.Open(ctx, filepath.Join(t.TempDir(), "controller.db")) + if err != nil { + t.Fatal(err) + } + defer func() { _ = db.Close() }() + intent := manifests.New(db) + vm := &protocol.Resource{APIVersion: "proxmox.openctl.io/v1", Kind: "VirtualMachine", Metadata: protocol.ResourceMetadata{Name: "runner"}, Spec: map[string]any{"node": "pve-test"}} + if err := intent.Save(ctx, vm); err != nil { + t.Fatal(err) + } + snapshot, err := intent.ExportMigration(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name) + if err != nil { + t.Fatal(err) + } + if err := intent.Forget(ctx, snapshot.Resource, snapshot.ControllerID, snapshot.Digest); err != nil { + t.Fatal(err) + } + ops := New(db, 0) + for _, operationType := range []string{TypeApply, TypeDelete} { + _, err := ops.Submit(ctx, &Operation{Type: operationType, APIVersion: vm.APIVersion, Kind: vm.Kind, ResourceName: vm.Metadata.Name, Source: manifests.SourceGitOps}) + var forgotten *manifests.ForgottenError + if !errors.As(err, &forgotten) { + t.Fatalf("%s could be queued after release: %v", operationType, err) + } + } + var queued int + if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM operations WHERE status IN ('pending', 'running')`).Scan(&queued); err != nil { + t.Fatal(err) + } + if queued != 0 { + t.Fatalf("release rejection left %d runnable operations", queued) + } + if err := intent.Adopt(ctx, snapshot.Resource); err != nil { + t.Fatal(err) + } + accepted, err := ops.Submit(ctx, &Operation{Type: TypeApply, APIVersion: vm.APIVersion, Kind: vm.Kind, ResourceName: vm.Metadata.Name, Source: manifests.SourceCLI}) + if err != nil { + t.Fatal(err) + } + if accepted.Status != StatusPending { + t.Fatalf("explicit reclamation did not restore ordinary management: %s", accepted.Status) + } +} diff --git a/internal/controller/operations/operations.go b/internal/controller/operations/operations.go index 451d949..9e518e8 100644 --- a/internal/controller/operations/operations.go +++ b/internal/controller/operations/operations.go @@ -18,6 +18,8 @@ import ( "fmt" "strings" "time" + + "github.com/openctl/openctl/internal/controller/manifests" ) // Op type values. Kept as string constants so the wire format and the SQL @@ -126,6 +128,10 @@ func (s *Store) Submit(ctx context.Context, op *Operation) (*Operation, error) { } defer func() { _ = tx.Rollback() }() + if err := manifests.CheckNotForgotten(ctx, tx, op.APIVersion, op.Kind, op.ResourceName); err != nil { + return nil, err + } + // Fail-fast concurrency check inside the transaction. var inflightID sql.NullString err = tx.QueryRowContext(ctx, diff --git a/internal/controller/providers/migration.go b/internal/controller/providers/migration.go new file mode 100644 index 0000000..2d5e294 --- /dev/null +++ b/internal/controller/providers/migration.go @@ -0,0 +1,14 @@ +package providers + +import ( + "context" + + "github.com/openctl/openctl/pkg/protocol" +) + +// MigrationIdentityReader verifies an existing resource through read-only +// provider calls. Identity must bind the actual instance, not only its name. +// Providers without this capability cannot export or adopt resources. +type MigrationIdentityReader interface { + MigrationIdentity(ctx context.Context, manifest *protocol.Resource) (string, error) +} diff --git a/internal/controller/providers/proxmox/migration.go b/internal/controller/providers/proxmox/migration.go new file mode 100644 index 0000000..c115bce --- /dev/null +++ b/internal/controller/providers/proxmox/migration.go @@ -0,0 +1,74 @@ +package proxmox + +import ( + "context" + "fmt" + "strings" + + "github.com/google/uuid" + + "github.com/openctl/openctl/internal/controller/providers" + "github.com/openctl/openctl/pkg/protocol" + pmclient "github.com/openctl/openctl/pkg/proxmox/client" +) + +var _ providers.MigrationIdentityReader = (*Provider)(nil) + +// MigrationIdentity binds the selected endpoint's real node, VMID and SMBIOS +// UUID. It never applies configuration, and never invents a missing UUID. +func (p *Provider) MigrationIdentity(ctx context.Context, manifest *protocol.Resource) (string, error) { + if manifest == nil || manifest.Kind != kindVM || manifest.Metadata.Name == "" { + return "", fmt.Errorf("migration requires a named VirtualMachine") + } + contextName, _ := manifest.Spec["context"].(string) + h, resolved, err := p.handlerForContext(contextName) + if err != nil { + return "", err + } + vms, err := h.Client().ListVMs(ctx) + if err != nil { + return "", fmt.Errorf("locate migration VM: %w", err) + } + var vm *pmclient.VM + for _, candidate := range vms { + if candidate.Name != manifest.Metadata.Name { + continue + } + if vm != nil { + return "", fmt.Errorf("migration VM name is ambiguous on selected endpoint") + } + vm = candidate + } + if vm == nil { + return "", providers.NotFound(kindVM, manifest.Metadata.Name) + } + if vm.Template != 0 || vm.VMID <= 0 || vm.Node == "" { + return "", fmt.Errorf("migration requires an existing non-template VM with node and VMID") + } + node, _ := manifest.Spec["node"].(string) + if node == "" || node != vm.Node { + return "", fmt.Errorf("migration VM node does not match desired node") + } + config, err := h.Client().GetVMConfigRaw(ctx, vm.Node, vm.VMID) + if err != nil { + return "", fmt.Errorf("read migration VM configuration: %w", err) + } + smbios, _ := config["smbios1"].(string) + var rawUUID string + for part := range strings.SplitSeq(smbios, ",") { + key, value, ok := strings.Cut(strings.TrimSpace(part), "=") + if ok && key == "uuid" { + if rawUUID != "" { + return "", fmt.Errorf("migration VM has duplicate SMBIOS UUIDs") + } + rawUUID = value + } + } + id, err := uuid.Parse(rawUUID) + if err != nil || id == uuid.Nil { + return "", fmt.Errorf("migration VM has missing or invalid smbios1 UUID") + } + // Seed normal Get/action routing only after verifying this exact context. + p.index.Store(manifest.Metadata.Name, resolved) + return fmt.Sprintf("proxmox:%s:%d:%s", vm.Node, vm.VMID, id.String()), nil +} diff --git a/internal/controller/providers/proxmox/migration_test.go b/internal/controller/providers/proxmox/migration_test.go new file mode 100644 index 0000000..00f07aa --- /dev/null +++ b/internal/controller/providers/proxmox/migration_test.go @@ -0,0 +1,80 @@ +package proxmox + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/openctl/openctl/pkg/protocol" +) + +func TestMigrationIdentityReadsSelectedContextAndActualUUID(t *testing.T) { + wrong := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Errorf("migration scanned the wrong context: %s", r.URL.Path) + http.Error(w, "wrong context", http.StatusInternalServerError) + })) + defer wrong.Close() + for _, tc := range []struct { + name, smbios, node string + template, count int + wantErr bool + }{ + {"actual identity", "uuid=8E0DA4E0-01EF-441E-A64A-75DB8614A535,manufacturer=example", "pve1", 0, 1, false}, + {"missing UUID", "manufacturer=example", "pve1", 0, 1, true}, + {"invalid UUID", "uuid=not-a-uuid", "pve1", 0, 1, true}, + {"nil UUID", "uuid=00000000-0000-0000-0000-000000000000", "pve1", 0, 1, true}, + {"duplicate UUID", "uuid=8e0da4e0-01ef-441e-a64a-75db8614a535,uuid=8e0da4e0-01ef-441e-a64a-75db8614a535", "pve1", 0, 1, true}, + {"wrong node", "uuid=8e0da4e0-01ef-441e-a64a-75db8614a535", "other", 0, 1, true}, + {"template", "uuid=8e0da4e0-01ef-441e-a64a-75db8614a535", "pve1", 1, 1, true}, + {"missing VM", "uuid=8e0da4e0-01ef-441e-a64a-75db8614a535", "pve1", 0, 0, true}, + {"ambiguous VM", "uuid=8e0da4e0-01ef-441e-a64a-75db8614a535", "pve1", 0, 2, true}, + } { + t.Run(tc.name, func(t *testing.T) { + reads := 0 + right := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + t.Errorf("migration mutated provider: %s %s", r.Method, r.URL.Path) + http.Error(w, "mutation forbidden", http.StatusMethodNotAllowed) + return + } + reads++ + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/api2/json/nodes": + _, _ = w.Write([]byte(`{"data":[{"node":"pve1"}]}`)) + case "/api2/json/nodes/pve1/qemu": + vms := []any{} + for i := range tc.count { + vms = append(vms, map[string]any{"name": "existing", "vmid": 101 + i, "template": tc.template}) + } + _ = json.NewEncoder(w).Encode(map[string]any{"data": vms}) + case "/api2/json/nodes/pve1/qemu/101/config": + _ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"smbios1": tc.smbios}}) + default: + t.Errorf("unexpected identity read: %s", r.URL.Path) + http.NotFound(w, r) + } + })) + defer right.Close() + p := NewMulti(map[string]*Config{ + "source": {Endpoint: wrong.URL, TokenID: "test", TokenSecret: "test"}, + "destination": {Endpoint: right.URL, TokenID: "test", TokenSecret: "test"}, + }, "source") + identity, err := p.MigrationIdentity(context.Background(), &protocol.Resource{ + Kind: kindVM, Metadata: protocol.ResourceMetadata{Name: "existing"}, + Spec: map[string]any{"context": "destination", "node": tc.node}, + }) + if (err != nil) != tc.wantErr { + t.Fatalf("identity error = %v, wantErr=%v", err, tc.wantErr) + } + if !tc.wantErr && identity != "proxmox:pve1:101:8e0da4e0-01ef-441e-a64a-75db8614a535" { + t.Fatalf("identity = %q", identity) + } + if reads == 0 { + t.Fatal("identity was fabricated without reading provider") + } + }) + } +} diff --git a/internal/controller/server/migration.go b/internal/controller/server/migration.go new file mode 100644 index 0000000..cd500f4 --- /dev/null +++ b/internal/controller/server/migration.go @@ -0,0 +1,254 @@ +package server + +import ( + "context" + "errors" + "fmt" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + + "github.com/openctl/openctl/internal/controller/auth" + "github.com/openctl/openctl/internal/controller/manifests" + "github.com/openctl/openctl/internal/controller/providers" + "github.com/openctl/openctl/internal/schema" + apiv1 "github.com/openctl/openctl/pkg/api/v1" + "github.com/openctl/openctl/pkg/protocol" +) + +const migrationBundleVersion = 1 + +func (h *resourceHandler) ExportResource(ctx context.Context, req *apiv1.ExportResourceRequest) (*apiv1.ExportResourceResponse, error) { + if err := authorize(ctx, auth.RoleAdmin); err != nil { + return nil, err + } + h.managementMu.Lock() + defer h.managementMu.Unlock() + if err := h.migrationReady(); err != nil { + return nil, err + } + if req.GetApiVersion() == "" || req.GetKind() == "" || req.GetName() == "" { + return nil, status.Error(codes.InvalidArgument, "api_version, kind, and name are required") + } + snapshot, err := h.manifests.ExportMigration(ctx, req.GetApiVersion(), req.GetKind(), req.GetName()) + if err != nil { + return nil, migrationError(err) + } + wire, err := resourceToProto(snapshot.Resource) + if err != nil { + return nil, status.Error(codes.Internal, "cannot encode migration manifest") + } + manifest, p, err := h.migrationResource(wire) + if err != nil { + return nil, err + } + identity, err := p.(providers.MigrationIdentityReader).MigrationIdentity(ctx, manifest) + if err != nil { + return nil, migrationIdentityError(err) + } + if identity == "" { + return nil, status.Error(codes.FailedPrecondition, "provider returned an empty migration identity") + } + return &apiv1.ExportResourceResponse{Bundle: &apiv1.MigrationBundle{ + Version: migrationBundleVersion, Resource: wire, ControllerId: snapshot.ControllerID, + ManifestDigest: snapshot.Digest, Identity: identity, + }}, nil +} + +func (h *resourceHandler) AdoptResource(ctx context.Context, req *apiv1.AdoptResourceRequest) (*apiv1.AdoptResourceResponse, error) { + if err := authorize(ctx, auth.RoleAdmin); err != nil { + return nil, err + } + h.managementMu.Lock() + defer h.managementMu.Unlock() + if err := h.migrationReady(); err != nil { + return nil, err + } + if (req.GetResource() == nil) == (req.GetBundle() == nil) { + return nil, status.Error(codes.InvalidArgument, "exactly one resource or bundle is required") + } + wire := req.GetResource() + if req.GetBundle() != nil { + if err := validateMigrationBundle(req.GetBundle()); err != nil { + return nil, err + } + wire = req.GetBundle().GetResource() + } + // Clone the wire form before any destination override; source forget must + // continue to use the original manifest and its digest. + wire = proto.Clone(wire).(*apiv1.Resource) + manifest, p, err := h.migrationResource(wire) + if err != nil { + return nil, err + } + if req.GetProviderContext() != "" { + manifest.Spec["context"] = req.GetProviderContext() + if err := schema.Validate(manifest); err != nil { + return nil, status.Error(codes.InvalidArgument, "invalid destination provider context") + } + } + identity, err := p.(providers.MigrationIdentityReader).MigrationIdentity(ctx, manifest) + if err != nil { + return nil, migrationIdentityError(err) + } + if identity == "" || (req.GetBundle() != nil && identity != req.GetBundle().GetIdentity()) { + return nil, status.Error(codes.FailedPrecondition, "existing VM identity does not match migration bundle") + } + // Identity and observation must both succeed before recording intent. + // A read outage must not be replaced with fabricated observed state. + observed, err := p.Get(ctx, manifest.Kind, manifest.Metadata.Name) + if err != nil || observed == nil { + return nil, status.Error(codes.FailedPrecondition, "cannot observe existing VM; retry adoption before forgetting the source") + } + out, err := resourceToProto(observed) + if err != nil { + return nil, status.Error(codes.Internal, "cannot encode observed VM") + } + desiredSpec, observedSpec := providers.DriftSpecs(p, manifest.Kind, manifest.Spec, observed.Spec) + out.Drift = computeDrift(desiredSpec, observedSpec) + if err := h.manifests.Adopt(ctx, manifest); err != nil { + return nil, migrationError(err) + } + if h.mirror != nil { + if err := h.mirror.Materialize(ctx, manifest); err != nil { + return nil, status.Error(codes.Internal, "VM adoption saved; disk projection failed, retry adoption to repair") + } + } + return &apiv1.AdoptResourceResponse{Resource: out, Message: "existing VM adopted without provider changes"}, nil +} + +func (h *resourceHandler) ForgetResource(ctx context.Context, req *apiv1.ForgetResourceRequest) (*apiv1.ForgetResourceResponse, error) { + if err := authorize(ctx, auth.RoleAdmin); err != nil { + return nil, err + } + h.managementMu.Lock() + defer h.managementMu.Unlock() + if err := h.migrationReady(); err != nil { + return nil, err + } + bundle := req.GetBundle() + if err := validateMigrationBundle(bundle); err != nil { + return nil, err + } + manifest, p, err := h.migrationResource(bundle.GetResource()) + if err != nil { + return nil, err + } + identity, err := p.(providers.MigrationIdentityReader).MigrationIdentity(ctx, manifest) + if err != nil { + return nil, migrationIdentityError(err) + } + if identity != bundle.GetIdentity() { + return nil, status.Error(codes.FailedPrecondition, "existing VM identity does not match migration bundle") + } + if err := h.manifests.Forget(ctx, manifest, bundle.GetControllerId(), bundle.GetManifestDigest()); err != nil { + return nil, migrationError(err) + } + if h.mirror != nil { + if err := h.mirror.RemoveProjection(ctx, manifest.APIVersion, manifest.Kind, manifest.Metadata.Name); err != nil { + return nil, status.Error(codes.Internal, "VM tracking released; disk projection failed, retry forget to repair") + } + } + return &apiv1.ForgetResourceResponse{Message: "controller tracking released; provider VM unchanged"}, nil +} + +func (h *resourceHandler) migrationReady() error { + if h.gitopsEnabled { + return status.Error(codes.FailedPrecondition, "disable manifest GitOps watch/pull automation and restart the controller before migration") + } + if h.manifests == nil { + return status.Error(codes.FailedPrecondition, "migration requires a configured manifest store") + } + return nil +} + +// migrationResource rejects relationship/state fields before proto conversion +// (which intentionally drops wire-only ownership and child fields). +func (h *resourceHandler) migrationResource(wire *apiv1.Resource) (*protocol.Resource, providers.Provider, error) { + if wire == nil || wire.GetApiVersion() == "" || wire.GetKind() == "" || wire.GetMetadata().GetName() == "" || wire.GetSpec() == nil { + return nil, nil, status.Error(codes.InvalidArgument, "complete desired resource is required") + } + if wire.GetStatus() != nil || len(wire.GetDrift()) != 0 { + return nil, nil, status.Error(codes.InvalidArgument, "migration accepts desired intent without status or drift") + } + if len(wire.GetChildren()) != 0 || len(wire.GetMetadata().GetOwnerRefs()) != 0 { + return nil, nil, status.Error(codes.FailedPrecondition, "migration does not support owned or composite resources") + } + manifest := protoToResource(wire) + if manifest.APIVersion != "proxmox.openctl.io/v1" || manifest.Kind != "VirtualMachine" { + return nil, nil, status.Error(codes.FailedPrecondition, "migration supports only standalone Proxmox VirtualMachine resources") + } + if err := schema.Validate(manifest); err != nil { + return nil, nil, status.Error(codes.InvalidArgument, "migration manifest failed schema validation") + } + p, err := h.registry.For(manifest.APIVersion) + if err != nil { + return nil, nil, status.Error(codes.FailedPrecondition, "migration provider is not configured") + } + if _, ok := p.(providers.MigrationIdentityReader); !ok { + return nil, nil, status.Error(codes.FailedPrecondition, "provider does not support migration identity verification") + } + if _, composite := p.(providers.Planner); composite { + return nil, nil, status.Error(codes.FailedPrecondition, "migration does not support composite providers") + } + if _, _, owned := h.registry.OwnerOf(manifest.Kind, manifest.Metadata.Name); owned || + len(h.registry.ChildrenOf(manifest.Kind, manifest.Metadata.Name)) != 0 || + manifest.Metadata.Labels[providers.LabelOwnerKind] != "" || manifest.Metadata.Labels[providers.LabelOwnerName] != "" { + return nil, nil, status.Error(codes.FailedPrecondition, "migration does not support owned or composite resources") + } + return manifest, p, nil +} + +func validateMigrationBundle(bundle *apiv1.MigrationBundle) error { + if bundle == nil || bundle.GetVersion() != migrationBundleVersion || bundle.GetResource() == nil || + bundle.GetControllerId() == "" || bundle.GetManifestDigest() == "" || bundle.GetIdentity() == "" { + return status.Error(codes.InvalidArgument, "complete version 1 migration bundle is required") + } + digest, err := manifests.MigrationDigest(protoToResource(bundle.GetResource())) + if err != nil || digest != bundle.GetManifestDigest() { + return status.Error(codes.InvalidArgument, "migration bundle manifest digest does not match") + } + return nil +} + +func migrationError(err error) error { + var forgotten *manifests.ForgottenError + if errors.As(err, &forgotten) { + return status.Error(codes.FailedPrecondition, forgotten.Error()) + } + switch { + case errors.Is(err, manifests.ErrMigrationNotFound): + return status.Error(codes.NotFound, "desired resource not found") + case errors.Is(err, manifests.ErrMigrationBusy): + return status.Error(codes.FailedPrecondition, "migration requires an idle controller; wait for all pending and running operations to finish") + case errors.Is(err, manifests.ErrMigrationUnsupported): + return status.Error(codes.FailedPrecondition, "migration requires a standalone VM without managed references, ownership, or opaque provider state") + case errors.Is(err, manifests.ErrMigrationConflict): + return status.Error(codes.FailedPrecondition, "migration conflicts with current desired intent or source controller; use an unchanged bundle from this source, or export current intent again") + default: + return status.Error(codes.Internal, "migration manifest store operation failed") + } +} + +func migrationIdentityError(err error) error { + var notFound *providers.NotFoundError + if errors.As(err, ¬Found) { + return status.Error(codes.NotFound, "existing migration VM not found") + } + return status.Error(codes.FailedPrecondition, "cannot verify existing migration VM identity; check provider credentials/context, desired node, and the VM's actual smbios1 UUID") +} + +func (h *resourceHandler) checkNotForgotten(ctx context.Context, av, kind, name string) error { + if h.manifests == nil { + return nil + } + forgotten, err := h.manifests.IsForgotten(ctx, av, kind, name) + if err != nil { + return status.Error(codes.Internal, "cannot check released resource tracking") + } + if forgotten { + return status.Error(codes.FailedPrecondition, fmt.Sprintf("%s %q was forgotten; explicitly adopt it before managing it", kind, name)) + } + return nil +} diff --git a/internal/controller/server/migration_concurrency_test.go b/internal/controller/server/migration_concurrency_test.go new file mode 100644 index 0000000..fc1948b --- /dev/null +++ b/internal/controller/server/migration_concurrency_test.go @@ -0,0 +1,93 @@ +package server + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "sync" + "testing" + "time" + + "github.com/openctl/openctl/internal/controller/providers" + pmprovider "github.com/openctl/openctl/internal/controller/providers/proxmox" + apiv1 "github.com/openctl/openctl/pkg/api/v1" +) + +func TestForgetWaitsForAnAlreadyStartedRuntimeAction(t *testing.T) { + ctx := context.Background() + started := make(chan struct{}) + finish := make(chan struct{}) + var release sync.Once + releaseAction := func() { release.Do(func() { close(finish) }) } + pve := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/api2/json/nodes": + _ = json.NewEncoder(w).Encode(map[string]any{"data": []map[string]any{{"node": "pve1"}}}) + case "/api2/json/nodes/pve1/qemu": + _ = json.NewEncoder(w).Encode(map[string]any{"data": []map[string]any{{"name": "existing", "vmid": 101, "status": "stopped"}}}) + case "/api2/json/nodes/pve1/qemu/101/config": + _ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"name": "existing", "cores": 2, "memory": "2048", "smbios1": "uuid=8e0da4e0-01ef-441e-a64a-75db8614a535"}}) + case "/api2/json/nodes/pve1/qemu/101/status/start": + close(started) + <-finish + _ = json.NewEncoder(w).Encode(map[string]any{"data": "UPID:runtime-action"}) + default: + http.NotFound(w, r) + } + })) + defer pve.Close() + defer releaseAction() + reg := providers.NewRegistry() + reg.Register(pmprovider.NewMulti(map[string]*pmprovider.Config{"source": {Endpoint: pve.URL, TokenID: "fixture", TokenSecret: "fixture"}}, "source")) + h, _, _ := migrationFixture(t, reg) + vm := migrationVM() + if err := h.mirror.Save(ctx, vm); err != nil { + t.Fatal(err) + } + exported, err := h.ExportResource(ctx, &apiv1.ExportResourceRequest{ApiVersion: vm.APIVersion, Kind: vm.Kind, Name: vm.Metadata.Name}) + if err != nil { + t.Fatal(err) + } + actionResult := make(chan error, 1) + go func() { + _, err := h.InvokeAction(ctx, &apiv1.InvokeActionRequest{ApiVersion: vm.APIVersion, Kind: vm.Kind, ResourceName: vm.Metadata.Name, Action: "start"}) + actionResult <- err + }() + select { + case <-started: + case <-time.After(5 * time.Second): + t.Fatal("runtime action did not start") + } + forgetResult := make(chan error, 1) + go func() { + _, err := h.ForgetResource(ctx, &apiv1.ForgetResourceRequest{Bundle: exported.Bundle}) + forgetResult <- err + }() + select { + case err := <-forgetResult: + t.Fatalf("ownership release returned while an earlier VM action was still in flight: %v", err) + case <-time.After(100 * time.Millisecond): + } + releaseAction() + select { + case err := <-actionResult: + if err != nil { + t.Fatal(err) + } + case <-time.After(5 * time.Second): + t.Fatal("runtime action did not finish") + } + select { + case err := <-forgetResult: + if err != nil { + t.Fatal(err) + } + case <-time.After(5 * time.Second): + t.Fatal("ownership release did not finish after the VM action") + } + if desired, err := h.manifests.Load(ctx, vm.APIVersion, vm.Kind, vm.Metadata.Name); err != nil || desired != nil { + t.Fatalf("release did not remove managed intent: %v, %v", desired, err) + } +} diff --git a/internal/controller/server/migration_test.go b/internal/controller/server/migration_test.go new file mode 100644 index 0000000..cb093a1 --- /dev/null +++ b/internal/controller/server/migration_test.go @@ -0,0 +1,375 @@ +package server + +import ( + "context" + "database/sql" + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/openctl/openctl/internal/controller/auth" + "github.com/openctl/openctl/internal/controller/manifests" + "github.com/openctl/openctl/internal/controller/operations" + "github.com/openctl/openctl/internal/controller/providers" + pmprovider "github.com/openctl/openctl/internal/controller/providers/proxmox" + "github.com/openctl/openctl/internal/controller/storage" + apiv1 "github.com/openctl/openctl/pkg/api/v1" + "github.com/openctl/openctl/pkg/protocol" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +func migrationVM() *protocol.Resource { + return &protocol.Resource{ + APIVersion: "proxmox.openctl.io/v1", Kind: "VirtualMachine", + Metadata: protocol.ResourceMetadata{Name: "existing", Annotations: map[string]string{"example.io/note": "desired intent"}}, + Spec: map[string]any{ + "context": "source", "node": "pve1", "template": map[string]any{"name": "ubuntu"}, + "cpu": map[string]any{"cores": 4}, "memory": map[string]any{"size": 2048}, + "cloudInit": map[string]any{"user": "ubuntu", "password": "literal-sensitive-password"}, + }, + } +} + +func migrationFixture(t *testing.T, reg *providers.Registry) (*resourceHandler, *sql.DB, *operations.Store) { + t.Helper() + db, err := storage.Open(context.Background(), filepath.Join(t.TempDir(), "state.db")) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = db.Close() }) + store := manifests.New(db) + h := newResourceHandler(reg, nil, nil, store) + h.mirror = manifests.NewDiskMirror(store, t.TempDir()) + return h, db, operations.New(db, 50) +} + +// Real Proxmox decoding and real SQLite are used here: forwarding-only mocks +// would not catch accidental Apply/resize or exporting observation as intent. +func TestMigrationExportAdoptForgetPreservesLiveVM(t *testing.T) { + ctx := context.Background() + uuid := "8e0da4e0-01ef-441e-a64a-75db8614a535" + mutations := 0 + pve := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodGet { + mutations++ + t.Errorf("migration mutated Proxmox: %s %s", req.Method, req.URL.Path) + http.Error(w, "read-only VM", http.StatusMethodNotAllowed) + return + } + w.Header().Set("Content-Type", "application/json") + switch req.URL.Path { + case "/api2/json/nodes": + _, _ = w.Write([]byte(`{"data":[{"node":"pve1"}]}`)) + case "/api2/json/nodes/pve1/qemu": + _, _ = w.Write([]byte(`{"data":[{"name":"existing","vmid":101,"status":"stopped"}]}`)) + case "/api2/json/nodes/pve1/qemu/101/config": + _ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{ + "smbios1": "uuid=" + uuid, "cores": 2, "sockets": 1, "memory": "2048", "name": "existing", + }}) + default: + t.Errorf("unexpected provider read: %s", req.URL.Path) + http.NotFound(w, req) + } + })) + defer pve.Close() + registry := func(contextName string) *providers.Registry { + reg := providers.NewRegistry() + reg.Register(pmprovider.NewMulti(map[string]*pmprovider.Config{ + contextName: {Endpoint: pve.URL, TokenID: "test", TokenSecret: "test"}, + }, contextName)) + return reg + } + source, _, _ := migrationFixture(t, registry("source")) + destination, _, _ := migrationFixture(t, registry("destination")) + desired := migrationVM() + if err := source.mirror.Save(ctx, desired); err != nil { + t.Fatal(err) + } + exported, err := source.ExportResource(ctx, &apiv1.ExportResourceRequest{ApiVersion: desired.APIVersion, Kind: desired.Kind, Name: desired.Metadata.Name}) + if err != nil { + t.Fatal(err) + } + bundle := exported.GetBundle() + original := proto.Clone(bundle).(*apiv1.MigrationBundle) + digest, _ := manifests.MigrationDigest(desired) + if bundle.GetManifestDigest() != digest || bundle.GetResource().GetStatus() != nil || bundle.GetResource().GetSpec().AsMap()["cpu"].(map[string]any)["cores"] != float64(4) { + t.Fatal("export must preserve complete desired intent rather than live CPU configuration") + } + if bundle.GetResource().GetMetadata().GetAnnotations()["example.io/note"] != "desired intent" { + t.Fatal("export dropped desired annotations") + } + if stillManaged, err := source.manifests.Load(ctx, desired.APIVersion, desired.Kind, desired.Metadata.Name); err != nil || stillManaged == nil { + t.Fatalf("export released ownership: %v", err) + } + request := &apiv1.AdoptResourceRequest{Bundle: bundle, ProviderContext: "destination"} + uuid = "58bc2d47-5923-4a79-9d24-aa8f53a0ca7f" + if _, err := destination.AdoptResource(ctx, request); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("wrong UUID must fail safely: %v", err) + } + if saved, _ := destination.manifests.Load(ctx, desired.APIVersion, desired.Kind, desired.Metadata.Name); saved != nil { + t.Fatal("wrong UUID adoption persisted intent") + } + uuid = "8e0da4e0-01ef-441e-a64a-75db8614a535" + adopted, err := destination.AdoptResource(ctx, request) + if err != nil { + t.Fatal(err) + } + if len(adopted.GetResource().GetDrift()) == 0 { + t.Fatal("adoption must retain live CPU drift without resizing") + } + if !proto.Equal(bundle, original) { + t.Fatal("destination context override mutated the original source bundle") + } + if _, err := destination.AdoptResource(ctx, request); err != nil { + t.Fatalf("identical adoption retry: %v", err) + } + saved, err := destination.manifests.Load(ctx, desired.APIVersion, desired.Kind, desired.Metadata.Name) + if err != nil || saved.Spec["context"] != "destination" || saved.Metadata.Annotations["example.io/note"] != "desired intent" { + t.Fatalf("destination intent was not adopted exactly with context override: %v", err) + } + projection := filepath.Join(destination.mirror.Root(), destination.mirror.RelPathFor(desired.APIVersion, desired.Kind, desired.Metadata.Name)) + if _, err := os.Stat(projection); err != nil { + t.Fatalf("adoption did not materialize projection: %v", err) + } + conflicting := proto.Clone(bundle.GetResource()).(*apiv1.Resource) + conflicting.Metadata.Annotations["example.io/note"] = "different destination intent" + if _, err := destination.AdoptResource(ctx, &apiv1.AdoptResourceRequest{Resource: conflicting, ProviderContext: "destination"}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("different existing intent was replaced: %v", err) + } + foreign := proto.Clone(bundle).(*apiv1.MigrationBundle) + foreign.ControllerId = "another-controller" + if _, err := source.ForgetResource(ctx, &apiv1.ForgetResourceRequest{Bundle: foreign}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("foreign controller bundle released source: %v", err) + } + // Full metadata participates in stale-export detection. + desired.Metadata.Annotations["example.io/note"] = "newer intent" + if err := source.manifests.Save(ctx, desired); err != nil { + t.Fatal(err) + } + if _, err := source.ForgetResource(ctx, &apiv1.ForgetResourceRequest{Bundle: bundle}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("stale bundle forget: %v", err) + } + desired.Metadata.Annotations["example.io/note"] = "desired intent" + if err := source.manifests.Save(ctx, desired); err != nil { + t.Fatal(err) + } + for range 2 { + if _, err := source.ForgetResource(ctx, &apiv1.ForgetResourceRequest{Bundle: bundle}); err != nil { + t.Fatalf("forget/retry: %v", err) + } + } + if tracked, _ := source.manifests.Load(ctx, desired.APIVersion, desired.Kind, desired.Metadata.Name); tracked != nil { + t.Fatal("forget left source desired tracking") + } + projection = filepath.Join(source.mirror.Root(), source.mirror.RelPathFor(desired.APIVersion, desired.Kind, desired.Metadata.Name)) + if _, err := os.Stat(projection); !errors.Is(err, os.ErrNotExist) { + t.Fatalf("forget left disk projection: %v", err) + } + wire, _ := resourceToProto(desired) + if _, err := source.Apply(ctx, &apiv1.ApplyRequest{Resource: wire}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("forgotten Apply allowed: %v", err) + } + if _, err := source.Delete(ctx, &apiv1.DeleteRequest{ApiVersion: desired.APIVersion, Kind: desired.Kind, Name: desired.Metadata.Name}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("forgotten Delete allowed: %v", err) + } + if _, err := source.InvokeAction(ctx, &apiv1.InvokeActionRequest{ApiVersion: desired.APIVersion, Kind: desired.Kind, ResourceName: desired.Metadata.Name, Action: "start"}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("forgotten runtime action allowed: %v", err) + } + // Explicit ordinary-manifest adoption reclaims management without Apply. + if _, err := source.AdoptResource(ctx, &apiv1.AdoptResourceRequest{Resource: wire}); err != nil { + t.Fatalf("explicit manifest adoption: %v", err) + } + if released, err := source.manifests.IsForgotten(ctx, desired.APIVersion, desired.Kind, desired.Metadata.Name); err != nil || released { + t.Fatalf("explicit adoption did not clear release: %v", err) + } + if mutations != 0 { + t.Fatalf("provider mutations = %d", mutations) + } +} + +func TestMigrationRequiresAdminAndConfiguredStore(t *testing.T) { + h := newResourceHandler(providers.NewRegistry(), nil, nil, nil) + calls := []func(context.Context) error{ + func(ctx context.Context) error { + _, err := h.ExportResource(ctx, &apiv1.ExportResourceRequest{}) + return err + }, + func(ctx context.Context) error { + _, err := h.AdoptResource(ctx, &apiv1.AdoptResourceRequest{}) + return err + }, + func(ctx context.Context) error { + _, err := h.ForgetResource(ctx, &apiv1.ForgetResourceRequest{}) + return err + }, + } + for _, call := range calls { + for _, role := range []auth.Role{auth.RoleViewer, auth.RoleEditor} { + if err := call(ctxWithRole(role)); status.Code(err) != codes.PermissionDenied { + t.Fatalf("non-admin migration: %v", err) + } + } + if err := call(ctxWithRole(auth.RoleAdmin)); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("unconfigured migration store: %v", err) + } + } +} + +func TestMigrationRefusesRunningGitOpsAutomation(t *testing.T) { + h, _, _ := migrationFixture(t, providers.NewRegistry()) + h.gitopsEnabled = true + calls := []func() error{ + func() error { + _, err := h.ExportResource(context.Background(), &apiv1.ExportResourceRequest{}) + return err + }, + func() error { + _, err := h.AdoptResource(context.Background(), &apiv1.AdoptResourceRequest{}) + return err + }, + func() error { + _, err := h.ForgetResource(context.Background(), &apiv1.ForgetResourceRequest{}) + return err + }, + } + for _, call := range calls { + if err := call(); status.Code(err) != codes.FailedPrecondition || !strings.Contains(status.Convert(err).Message(), "restart") { + t.Fatalf("live GitOps must block migration with actionable restart instructions: %v", err) + } + } +} + +func TestMigrationMalformedAndUnsafeRequests(t *testing.T) { + h, _, ops := migrationFixture(t, providers.NewRegistry()) + ctx := context.Background() + wire, _ := resourceToProto(migrationVM()) + digest, _ := manifests.MigrationDigest(protoToResource(wire)) + bundle := &apiv1.MigrationBundle{Version: 1, Resource: wire, ControllerId: "source", ManifestDigest: digest, Identity: "identity"} + for _, tc := range []struct { + name string + request *apiv1.AdoptResourceRequest + code codes.Code + }{ + {"neither input", &apiv1.AdoptResourceRequest{}, codes.InvalidArgument}, + {"both inputs", &apiv1.AdoptResourceRequest{Resource: wire, Bundle: bundle}, codes.InvalidArgument}, + {"unknown bundle version", &apiv1.AdoptResourceRequest{Bundle: &apiv1.MigrationBundle{Version: 2}}, codes.InvalidArgument}, + {"tampered digest", &apiv1.AdoptResourceRequest{Bundle: &apiv1.MigrationBundle{Version: 1, Resource: wire, ControllerId: "source", ManifestDigest: strings.Repeat("0", 64), Identity: "identity"}}, codes.InvalidArgument}, + {"unconfigured provider", &apiv1.AdoptResourceRequest{Resource: wire}, codes.FailedPrecondition}, + } { + t.Run(tc.name, func(t *testing.T) { + if _, err := h.AdoptResource(ctx, tc.request); status.Code(err) != tc.code { + t.Fatalf("code=%v, want %v: %v", status.Code(err), tc.code, err) + } + }) + } + for _, mutate := range []func(*apiv1.Resource){ + func(r *apiv1.Resource) { r.Kind = "Cluster" }, + func(r *apiv1.Resource) { r.Children = []*apiv1.ResourceRef{{Kind: "VirtualMachine", Name: "child"}} }, + func(r *apiv1.Resource) { r.Metadata.OwnerRefs = []*apiv1.ResourceRef{{Kind: "Cluster", Name: "owner"}} }, + } { + unsafe := proto.Clone(wire).(*apiv1.Resource) + mutate(unsafe) + if _, err := h.AdoptResource(ctx, &apiv1.AdoptResourceRequest{Resource: unsafe}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("unsafe resource not refused: %v", err) + } + } + if err := h.manifests.Save(ctx, migrationVM()); err != nil { + t.Fatal(err) + } + if _, err := ops.Submit(ctx, &operations.Operation{Type: operations.TypeApply, APIVersion: "proxmox.openctl.io/v1", Kind: "VirtualMachine", ResourceName: "unrelated", ManifestJSON: `{}`}); err != nil { + t.Fatal(err) + } + if _, err := h.ExportResource(ctx, &apiv1.ExportResourceRequest{ApiVersion: wire.GetApiVersion(), Kind: wire.GetKind(), Name: wire.GetMetadata().GetName()}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("globally busy controller exported resource: %v", err) + } +} + +func TestMigrationRESTRoutesReachRegisteredHandlers(t *testing.T) { + base, token := startGatewayTestServer(t) + for _, route := range []string{"export", "adopt", "forget"} { + req, err := http.NewRequest(http.MethodPost, base+"/v1/resources:"+route, strings.NewReader(`{}`)) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + resp, err := http.DefaultClient.Do(req) // #nosec G704 -- in-process test server + if err != nil { + t.Fatal(err) + } + _ = resp.Body.Close() + if resp.StatusCode != http.StatusBadRequest { + // FailedPrecondition maps to 400: store isn't configured in fixture. + t.Fatalf("%s REST status=%d; handler must be registered", route, resp.StatusCode) + } + } +} + +type migrationUnsupportedProvider struct{ fakeProvider } + +func (*migrationUnsupportedProvider) Name() string { return "proxmox" } +func (*migrationUnsupportedProvider) Kinds() []string { return []string{"VirtualMachine"} } + +type migrationOwnedProvider struct { + migrationUnsupportedProvider + owned bool + children bool + identityReads int +} + +func (p *migrationOwnedProvider) MigrationIdentity(context.Context, *protocol.Resource) (string, error) { + p.identityReads++ + return "identity", nil +} + +func (p *migrationOwnedProvider) OwnerOf(string, string) (string, string, bool) { + return "Cluster", "owner", p.owned +} + +func (p *migrationOwnedProvider) ChildrenOf(string, string) []providers.ResourceRef { + if p.children { + return []providers.ResourceRef{{APIVersion: "proxmox.openctl.io/v1", Kind: "VirtualMachine", Name: "child"}} + } + return nil +} + +func TestMigrationRejectsProviderOwnershipAndMissingCapability(t *testing.T) { + for _, tc := range []struct { + name string + provider providers.Provider + label bool + }{ + {"missing identity capability", &migrationUnsupportedProvider{}, false}, + {"registry-owned VM", &migrationOwnedProvider{owned: true}, false}, + {"registry composite children", &migrationOwnedProvider{children: true}, false}, + {"owner-labeled VM", &migrationOwnedProvider{}, true}, + } { + t.Run(tc.name, func(t *testing.T) { + reg := providers.NewRegistry() + reg.Register(tc.provider) + h, _, _ := migrationFixture(t, reg) + desired := migrationVM() + if tc.label { + desired.Metadata.Labels = map[string]string{providers.LabelOwnerKind: "Cluster", providers.LabelOwnerName: "owner"} + } + wire, _ := resourceToProto(desired) + if _, err := h.AdoptResource(context.Background(), &apiv1.AdoptResourceRequest{Resource: wire}); status.Code(err) != codes.FailedPrecondition { + t.Fatalf("unsafe provider migration: %v", err) + } + if p, ok := tc.provider.(*migrationOwnedProvider); ok && p.identityReads != 0 { + t.Fatal("unsafe provider was consulted before rejecting ownership") + } + saved, err := h.manifests.Load(context.Background(), desired.APIVersion, desired.Kind, desired.Metadata.Name) + if err != nil || saved != nil { + t.Fatalf("unsafe provider adoption persisted state: %v", err) + } + }) + } +} diff --git a/internal/controller/server/resource.go b/internal/controller/server/resource.go index edc0aba..614a0b0 100644 --- a/internal/controller/server/resource.go +++ b/internal/controller/server/resource.go @@ -9,6 +9,7 @@ import ( "reflect" "slices" "strings" + "sync" "time" "google.golang.org/grpc/codes" @@ -57,9 +58,14 @@ type resourceHandler struct { registry *providers.Registry ops *operations.Store dispatcher *operations.Dispatcher + // Synchronous runtime actions must finish before migration can release + // ownership. Async operations are fenced by their SQLite admission checks. + managementMu sync.RWMutex // manifests is optional: when set, Get/List populate the drift field by // comparing observed state against the persisted desired manifest. - manifests *manifests.Store + manifests *manifests.Store + mirror *manifests.DiskMirror + gitopsEnabled bool } func newResourceHandler(reg *providers.Registry, ops *operations.Store, d *operations.Dispatcher, m *manifests.Store) *resourceHandler { @@ -70,6 +76,8 @@ func (h *resourceHandler) Apply(ctx context.Context, req *apiv1.ApplyRequest) (* if err := authorize(ctx, auth.RoleEditor); err != nil { return nil, err } + h.managementMu.RLock() + defer h.managementMu.RUnlock() if req.GetResource() == nil { return nil, status.Error(codes.InvalidArgument, "resource is required") } @@ -85,6 +93,9 @@ func (h *resourceHandler) Apply(ctx context.Context, req *apiv1.ApplyRequest) (* if manifest.Metadata.Name == "" { return nil, status.Error(codes.InvalidArgument, "metadata.name is required") } + if err := h.checkNotForgotten(ctx, manifest.APIVersion, manifest.Kind, manifest.Metadata.Name); err != nil { + return nil, err + } // Phase 5: surface the destructive flags to providers via annotations. // Annotations ride along on manifest_json through the operations table, @@ -120,6 +131,10 @@ func (h *resourceHandler) Apply(ctx context.Context, req *apiv1.ApplyRequest) (* Source: sourceFromContext(ctx), }) if err != nil { + var forgotten *manifests.ForgottenError + if errors.As(err, &forgotten) { + return nil, status.Error(codes.FailedPrecondition, forgotten.Error()) + } var conflict *operations.ConflictError if errors.As(err, &conflict) { return nil, status.Errorf(codes.AlreadyExists, @@ -398,9 +413,14 @@ func (h *resourceHandler) InvokeAction(ctx context.Context, req *apiv1.InvokeAct if err := authorize(ctx, auth.RoleEditor); err != nil { return nil, err } + h.managementMu.RLock() + defer h.managementMu.RUnlock() if req.GetApiVersion() == "" || req.GetKind() == "" || req.GetResourceName() == "" || req.GetAction() == "" { return nil, status.Error(codes.InvalidArgument, "api_version, kind, resource_name, and action are required") } + if err := h.checkNotForgotten(ctx, req.GetApiVersion(), req.GetKind(), req.GetResourceName()); err != nil { + return nil, err + } result, err := h.registry.DoAction(ctx, req.GetApiVersion(), req.GetKind(), req.GetResourceName(), req.GetAction(), req.GetParameters()) if err != nil { // Route "not supported" errors to FailedPrecondition so the UI can @@ -838,9 +858,14 @@ func (h *resourceHandler) Delete(ctx context.Context, req *apiv1.DeleteRequest) if err := authorize(ctx, auth.RoleEditor); err != nil { return nil, err } + h.managementMu.RLock() + defer h.managementMu.RUnlock() if _, err := h.registry.For(req.GetApiVersion()); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } + if err := h.checkNotForgotten(ctx, req.GetApiVersion(), req.GetKind(), req.GetName()); err != nil { + return nil, err + } // Block-on-references: refuse to delete a resource owned by another // resource. Caller must delete the owner instead. (Architectural @@ -873,6 +898,10 @@ func (h *resourceHandler) Delete(ctx context.Context, req *apiv1.DeleteRequest) Source: sourceFromContext(ctx), }) if err != nil { + var forgotten *manifests.ForgottenError + if errors.As(err, &forgotten) { + return nil, status.Error(codes.FailedPrecondition, forgotten.Error()) + } var conflict *operations.ConflictError if errors.As(err, &conflict) { return nil, status.Errorf(codes.AlreadyExists, diff --git a/internal/controller/server/server.go b/internal/controller/server/server.go index dc7313f..2a34edd 100644 --- a/internal/controller/server/server.go +++ b/internal/controller/server/server.go @@ -63,6 +63,9 @@ type Options struct { // Phase U1.4). When set, the auth interceptor accepts session tokens // alongside the root bearer. Sessions *auth.SessionStore + // GitOpsEnabled reflects running manifest watch/pull automation. Migration + // requires it disabled to avoid projection changes racing local handoff. + GitOpsEnabled bool // DiskMirror is the on-disk projection of applied manifests (UI Phase // U2.1). When set, the controller surfaces it via RepoService so the // UI can show the directory + git status. @@ -119,7 +122,10 @@ func New(opts Options) (*Server, error) { if registry == nil { registry = providers.NewRegistry() } - apiv1.RegisterResourceServiceServer(g, newResourceHandler(registry, opts.Operations, opts.Dispatcher, opts.Manifests)) + resourceHandler := newResourceHandler(registry, opts.Operations, opts.Dispatcher, opts.Manifests) + resourceHandler.mirror = opts.DiskMirror + resourceHandler.gitopsEnabled = opts.GitOpsEnabled + apiv1.RegisterResourceServiceServer(g, resourceHandler) if opts.Operations != nil { // Guard against a nil *Dispatcher becoming a non-nil interface (which // would make CancelOperation attempt to cancel running ops on a nil diff --git a/internal/controller/storage/migrations/0012_resource_releases.sql b/internal/controller/storage/migrations/0012_resource_releases.sql new file mode 100644 index 0000000..03607dc --- /dev/null +++ b/internal/controller/storage/migrations/0012_resource_releases.sql @@ -0,0 +1,11 @@ +-- A release is deliberately separate from desired state. It fences stale +-- GitOps files and operation submissions until an explicit adoption reclaims +-- the resource; neither a release nor its removal changes infrastructure. +CREATE TABLE resource_releases ( + api_version TEXT NOT NULL, + kind TEXT NOT NULL, + name TEXT NOT NULL, + manifest_digest TEXT NOT NULL, + released_at TEXT NOT NULL, + PRIMARY KEY (api_version, kind, name) +); diff --git a/pkg/api/v1/api.pb.go b/pkg/api/v1/api.pb.go index 6833abd..6e23e14 100644 --- a/pkg/api/v1/api.pb.go +++ b/pkg/api/v1/api.pb.go @@ -72,7 +72,7 @@ func (x WatchEvent_Type) Number() protoreflect.EnumNumber { // Deprecated: Use WatchEvent_Type.Descriptor instead. func (WatchEvent_Type) EnumDescriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{29, 0} + return file_api_proto_rawDescGZIP(), []int{36, 0} } type PingRequest struct { @@ -195,6 +195,390 @@ func (x *PingResponse) GetBuildTime() string { return "" } +// MigrationBundle contains desired intent, not observed state. Treat it as +// sensitive: manifests may contain literal cloud-init credentials. +type MigrationBundle struct { + state protoimpl.MessageState `protogen:"open.v1"` + Version uint32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"` + Resource *Resource `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + ControllerId string `protobuf:"bytes,3,opt,name=controller_id,json=controllerId,proto3" json:"controller_id,omitempty"` + ManifestDigest string `protobuf:"bytes,4,opt,name=manifest_digest,json=manifestDigest,proto3" json:"manifest_digest,omitempty"` + Identity string `protobuf:"bytes,5,opt,name=identity,proto3" json:"identity,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MigrationBundle) Reset() { + *x = MigrationBundle{} + mi := &file_api_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MigrationBundle) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MigrationBundle) ProtoMessage() {} + +func (x *MigrationBundle) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MigrationBundle.ProtoReflect.Descriptor instead. +func (*MigrationBundle) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{2} +} + +func (x *MigrationBundle) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *MigrationBundle) GetResource() *Resource { + if x != nil { + return x.Resource + } + return nil +} + +func (x *MigrationBundle) GetControllerId() string { + if x != nil { + return x.ControllerId + } + return "" +} + +func (x *MigrationBundle) GetManifestDigest() string { + if x != nil { + return x.ManifestDigest + } + return "" +} + +func (x *MigrationBundle) GetIdentity() string { + if x != nil { + return x.Identity + } + return "" +} + +type ExportResourceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ApiVersion string `protobuf:"bytes,1,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"` + Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"` + Name string `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExportResourceRequest) Reset() { + *x = ExportResourceRequest{} + mi := &file_api_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExportResourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportResourceRequest) ProtoMessage() {} + +func (x *ExportResourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportResourceRequest.ProtoReflect.Descriptor instead. +func (*ExportResourceRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{3} +} + +func (x *ExportResourceRequest) GetApiVersion() string { + if x != nil { + return x.ApiVersion + } + return "" +} + +func (x *ExportResourceRequest) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *ExportResourceRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ExportResourceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Bundle *MigrationBundle `protobuf:"bytes,1,opt,name=bundle,proto3" json:"bundle,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ExportResourceResponse) Reset() { + *x = ExportResourceResponse{} + mi := &file_api_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ExportResourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ExportResourceResponse) ProtoMessage() {} + +func (x *ExportResourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ExportResourceResponse.ProtoReflect.Descriptor instead. +func (*ExportResourceResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{4} +} + +func (x *ExportResourceResponse) GetBundle() *MigrationBundle { + if x != nil { + return x.Bundle + } + return nil +} + +type AdoptResourceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Exactly one of resource and bundle is required. + Resource *Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + Bundle *MigrationBundle `protobuf:"bytes,2,opt,name=bundle,proto3" json:"bundle,omitempty"` + // Overrides spec.context at the destination without rewriting the bundle. + ProviderContext string `protobuf:"bytes,3,opt,name=provider_context,json=providerContext,proto3" json:"provider_context,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdoptResourceRequest) Reset() { + *x = AdoptResourceRequest{} + mi := &file_api_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdoptResourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdoptResourceRequest) ProtoMessage() {} + +func (x *AdoptResourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdoptResourceRequest.ProtoReflect.Descriptor instead. +func (*AdoptResourceRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{5} +} + +func (x *AdoptResourceRequest) GetResource() *Resource { + if x != nil { + return x.Resource + } + return nil +} + +func (x *AdoptResourceRequest) GetBundle() *MigrationBundle { + if x != nil { + return x.Bundle + } + return nil +} + +func (x *AdoptResourceRequest) GetProviderContext() string { + if x != nil { + return x.ProviderContext + } + return "" +} + +type AdoptResourceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Resource *Resource `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + Message string `protobuf:"bytes,2,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AdoptResourceResponse) Reset() { + *x = AdoptResourceResponse{} + mi := &file_api_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AdoptResourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AdoptResourceResponse) ProtoMessage() {} + +func (x *AdoptResourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AdoptResourceResponse.ProtoReflect.Descriptor instead. +func (*AdoptResourceResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{6} +} + +func (x *AdoptResourceResponse) GetResource() *Resource { + if x != nil { + return x.Resource + } + return nil +} + +func (x *AdoptResourceResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +type ForgetResourceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Bundle *MigrationBundle `protobuf:"bytes,1,opt,name=bundle,proto3" json:"bundle,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ForgetResourceRequest) Reset() { + *x = ForgetResourceRequest{} + mi := &file_api_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ForgetResourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForgetResourceRequest) ProtoMessage() {} + +func (x *ForgetResourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ForgetResourceRequest.ProtoReflect.Descriptor instead. +func (*ForgetResourceRequest) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{7} +} + +func (x *ForgetResourceRequest) GetBundle() *MigrationBundle { + if x != nil { + return x.Bundle + } + return nil +} + +type ForgetResourceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ForgetResourceResponse) Reset() { + *x = ForgetResourceResponse{} + mi := &file_api_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ForgetResourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ForgetResourceResponse) ProtoMessage() {} + +func (x *ForgetResourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_api_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ForgetResourceResponse.ProtoReflect.Descriptor instead. +func (*ForgetResourceResponse) Descriptor() ([]byte, []int) { + return file_api_proto_rawDescGZIP(), []int{8} +} + +func (x *ForgetResourceResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + type ListActionsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` ApiVersion string `protobuf:"bytes,1,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"` @@ -205,7 +589,7 @@ type ListActionsRequest struct { func (x *ListActionsRequest) Reset() { *x = ListActionsRequest{} - mi := &file_api_proto_msgTypes[2] + mi := &file_api_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -217,7 +601,7 @@ func (x *ListActionsRequest) String() string { func (*ListActionsRequest) ProtoMessage() {} func (x *ListActionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[2] + mi := &file_api_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -230,7 +614,7 @@ func (x *ListActionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActionsRequest.ProtoReflect.Descriptor instead. func (*ListActionsRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{2} + return file_api_proto_rawDescGZIP(), []int{9} } func (x *ListActionsRequest) GetApiVersion() string { @@ -262,7 +646,7 @@ type ListActionsResponse struct { func (x *ListActionsResponse) Reset() { *x = ListActionsResponse{} - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -274,7 +658,7 @@ func (x *ListActionsResponse) String() string { func (*ListActionsResponse) ProtoMessage() {} func (x *ListActionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[3] + mi := &file_api_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -287,7 +671,7 @@ func (x *ListActionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListActionsResponse.ProtoReflect.Descriptor instead. func (*ListActionsResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{3} + return file_api_proto_rawDescGZIP(), []int{10} } func (x *ListActionsResponse) GetActions() []string { @@ -317,7 +701,7 @@ type ActionSpec struct { func (x *ActionSpec) Reset() { *x = ActionSpec{} - mi := &file_api_proto_msgTypes[4] + mi := &file_api_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -329,7 +713,7 @@ func (x *ActionSpec) String() string { func (*ActionSpec) ProtoMessage() {} func (x *ActionSpec) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[4] + mi := &file_api_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -342,7 +726,7 @@ func (x *ActionSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use ActionSpec.ProtoReflect.Descriptor instead. func (*ActionSpec) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{4} + return file_api_proto_rawDescGZIP(), []int{11} } func (x *ActionSpec) GetName() string { @@ -384,7 +768,7 @@ type ActionParameterSpec struct { func (x *ActionParameterSpec) Reset() { *x = ActionParameterSpec{} - mi := &file_api_proto_msgTypes[5] + mi := &file_api_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -396,7 +780,7 @@ func (x *ActionParameterSpec) String() string { func (*ActionParameterSpec) ProtoMessage() {} func (x *ActionParameterSpec) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[5] + mi := &file_api_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -409,7 +793,7 @@ func (x *ActionParameterSpec) ProtoReflect() protoreflect.Message { // Deprecated: Use ActionParameterSpec.ProtoReflect.Descriptor instead. func (*ActionParameterSpec) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{5} + return file_api_proto_rawDescGZIP(), []int{12} } func (x *ActionParameterSpec) GetName() string { @@ -464,7 +848,7 @@ type InvokeActionRequest struct { func (x *InvokeActionRequest) Reset() { *x = InvokeActionRequest{} - mi := &file_api_proto_msgTypes[6] + mi := &file_api_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -476,7 +860,7 @@ func (x *InvokeActionRequest) String() string { func (*InvokeActionRequest) ProtoMessage() {} func (x *InvokeActionRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[6] + mi := &file_api_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -489,7 +873,7 @@ func (x *InvokeActionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InvokeActionRequest.ProtoReflect.Descriptor instead. func (*InvokeActionRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{6} + return file_api_proto_rawDescGZIP(), []int{13} } func (x *InvokeActionRequest) GetApiVersion() string { @@ -544,7 +928,7 @@ type InvokeActionResponse struct { func (x *InvokeActionResponse) Reset() { *x = InvokeActionResponse{} - mi := &file_api_proto_msgTypes[7] + mi := &file_api_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -556,7 +940,7 @@ func (x *InvokeActionResponse) String() string { func (*InvokeActionResponse) ProtoMessage() {} func (x *InvokeActionResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[7] + mi := &file_api_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -569,7 +953,7 @@ func (x *InvokeActionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InvokeActionResponse.ProtoReflect.Descriptor instead. func (*InvokeActionResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{7} + return file_api_proto_rawDescGZIP(), []int{14} } func (x *InvokeActionResponse) GetMessage() string { @@ -611,7 +995,7 @@ type GetChildrenGraphRequest struct { func (x *GetChildrenGraphRequest) Reset() { *x = GetChildrenGraphRequest{} - mi := &file_api_proto_msgTypes[8] + mi := &file_api_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -623,7 +1007,7 @@ func (x *GetChildrenGraphRequest) String() string { func (*GetChildrenGraphRequest) ProtoMessage() {} func (x *GetChildrenGraphRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[8] + mi := &file_api_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -636,7 +1020,7 @@ func (x *GetChildrenGraphRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetChildrenGraphRequest.ProtoReflect.Descriptor instead. func (*GetChildrenGraphRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{8} + return file_api_proto_rawDescGZIP(), []int{15} } func (x *GetChildrenGraphRequest) GetApiVersion() string { @@ -685,7 +1069,7 @@ type GraphNode struct { func (x *GraphNode) Reset() { *x = GraphNode{} - mi := &file_api_proto_msgTypes[9] + mi := &file_api_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -697,7 +1081,7 @@ func (x *GraphNode) String() string { func (*GraphNode) ProtoMessage() {} func (x *GraphNode) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[9] + mi := &file_api_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -710,7 +1094,7 @@ func (x *GraphNode) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphNode.ProtoReflect.Descriptor instead. func (*GraphNode) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{9} + return file_api_proto_rawDescGZIP(), []int{16} } func (x *GraphNode) GetId() string { @@ -778,7 +1162,7 @@ type GraphEdge struct { func (x *GraphEdge) Reset() { *x = GraphEdge{} - mi := &file_api_proto_msgTypes[10] + mi := &file_api_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -790,7 +1174,7 @@ func (x *GraphEdge) String() string { func (*GraphEdge) ProtoMessage() {} func (x *GraphEdge) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[10] + mi := &file_api_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -803,7 +1187,7 @@ func (x *GraphEdge) ProtoReflect() protoreflect.Message { // Deprecated: Use GraphEdge.ProtoReflect.Descriptor instead. func (*GraphEdge) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{10} + return file_api_proto_rawDescGZIP(), []int{17} } func (x *GraphEdge) GetFrom() string { @@ -844,7 +1228,7 @@ type GetChildrenGraphResponse struct { func (x *GetChildrenGraphResponse) Reset() { *x = GetChildrenGraphResponse{} - mi := &file_api_proto_msgTypes[11] + mi := &file_api_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -856,7 +1240,7 @@ func (x *GetChildrenGraphResponse) String() string { func (*GetChildrenGraphResponse) ProtoMessage() {} func (x *GetChildrenGraphResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[11] + mi := &file_api_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -869,7 +1253,7 @@ func (x *GetChildrenGraphResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetChildrenGraphResponse.ProtoReflect.Descriptor instead. func (*GetChildrenGraphResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{11} + return file_api_proto_rawDescGZIP(), []int{18} } func (x *GetChildrenGraphResponse) GetNodes() []*GraphNode { @@ -895,7 +1279,7 @@ type DryRunApplyRequest struct { func (x *DryRunApplyRequest) Reset() { *x = DryRunApplyRequest{} - mi := &file_api_proto_msgTypes[12] + mi := &file_api_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -907,7 +1291,7 @@ func (x *DryRunApplyRequest) String() string { func (*DryRunApplyRequest) ProtoMessage() {} func (x *DryRunApplyRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[12] + mi := &file_api_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -920,7 +1304,7 @@ func (x *DryRunApplyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DryRunApplyRequest.ProtoReflect.Descriptor instead. func (*DryRunApplyRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{12} + return file_api_proto_rawDescGZIP(), []int{19} } func (x *DryRunApplyRequest) GetResource() *Resource { @@ -963,7 +1347,7 @@ type DryRunApplyResponse struct { func (x *DryRunApplyResponse) Reset() { *x = DryRunApplyResponse{} - mi := &file_api_proto_msgTypes[13] + mi := &file_api_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -975,7 +1359,7 @@ func (x *DryRunApplyResponse) String() string { func (*DryRunApplyResponse) ProtoMessage() {} func (x *DryRunApplyResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[13] + mi := &file_api_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -988,7 +1372,7 @@ func (x *DryRunApplyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DryRunApplyResponse.ProtoReflect.Descriptor instead. func (*DryRunApplyResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{13} + return file_api_proto_rawDescGZIP(), []int{20} } func (x *DryRunApplyResponse) GetDiff() []*DriftEntry { @@ -1046,7 +1430,7 @@ type FieldError struct { func (x *FieldError) Reset() { *x = FieldError{} - mi := &file_api_proto_msgTypes[14] + mi := &file_api_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1058,7 +1442,7 @@ func (x *FieldError) String() string { func (*FieldError) ProtoMessage() {} func (x *FieldError) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[14] + mi := &file_api_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1071,7 +1455,7 @@ func (x *FieldError) ProtoReflect() protoreflect.Message { // Deprecated: Use FieldError.ProtoReflect.Descriptor instead. func (*FieldError) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{14} + return file_api_proto_rawDescGZIP(), []int{21} } func (x *FieldError) GetPath() string { @@ -1100,7 +1484,7 @@ type ChildAction struct { func (x *ChildAction) Reset() { *x = ChildAction{} - mi := &file_api_proto_msgTypes[15] + mi := &file_api_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1112,7 +1496,7 @@ func (x *ChildAction) String() string { func (*ChildAction) ProtoMessage() {} func (x *ChildAction) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[15] + mi := &file_api_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1125,7 +1509,7 @@ func (x *ChildAction) ProtoReflect() protoreflect.Message { // Deprecated: Use ChildAction.ProtoReflect.Descriptor instead. func (*ChildAction) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{15} + return file_api_proto_rawDescGZIP(), []int{22} } func (x *ChildAction) GetVerb() string { @@ -1182,7 +1566,7 @@ type Resource struct { func (x *Resource) Reset() { *x = Resource{} - mi := &file_api_proto_msgTypes[16] + mi := &file_api_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1194,7 +1578,7 @@ func (x *Resource) String() string { func (*Resource) ProtoMessage() {} func (x *Resource) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[16] + mi := &file_api_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1207,7 +1591,7 @@ func (x *Resource) ProtoReflect() protoreflect.Message { // Deprecated: Use Resource.ProtoReflect.Descriptor instead. func (*Resource) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{16} + return file_api_proto_rawDescGZIP(), []int{23} } func (x *Resource) GetApiVersion() string { @@ -1273,7 +1657,7 @@ type ResourceRef struct { func (x *ResourceRef) Reset() { *x = ResourceRef{} - mi := &file_api_proto_msgTypes[17] + mi := &file_api_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1285,7 +1669,7 @@ func (x *ResourceRef) String() string { func (*ResourceRef) ProtoMessage() {} func (x *ResourceRef) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[17] + mi := &file_api_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1298,7 +1682,7 @@ func (x *ResourceRef) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceRef.ProtoReflect.Descriptor instead. func (*ResourceRef) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{17} + return file_api_proto_rawDescGZIP(), []int{24} } func (x *ResourceRef) GetApiVersion() string { @@ -1337,7 +1721,7 @@ type DriftEntry struct { func (x *DriftEntry) Reset() { *x = DriftEntry{} - mi := &file_api_proto_msgTypes[18] + mi := &file_api_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1349,7 +1733,7 @@ func (x *DriftEntry) String() string { func (*DriftEntry) ProtoMessage() {} func (x *DriftEntry) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[18] + mi := &file_api_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1362,7 +1746,7 @@ func (x *DriftEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use DriftEntry.ProtoReflect.Descriptor instead. func (*DriftEntry) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{18} + return file_api_proto_rawDescGZIP(), []int{25} } func (x *DriftEntry) GetPath() string { @@ -1402,7 +1786,7 @@ type Metadata struct { func (x *Metadata) Reset() { *x = Metadata{} - mi := &file_api_proto_msgTypes[19] + mi := &file_api_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1414,7 +1798,7 @@ func (x *Metadata) String() string { func (*Metadata) ProtoMessage() {} func (x *Metadata) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[19] + mi := &file_api_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1427,7 +1811,7 @@ func (x *Metadata) ProtoReflect() protoreflect.Message { // Deprecated: Use Metadata.ProtoReflect.Descriptor instead. func (*Metadata) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{19} + return file_api_proto_rawDescGZIP(), []int{26} } func (x *Metadata) GetName() string { @@ -1474,7 +1858,7 @@ type ApplyRequest struct { func (x *ApplyRequest) Reset() { *x = ApplyRequest{} - mi := &file_api_proto_msgTypes[20] + mi := &file_api_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1486,7 +1870,7 @@ func (x *ApplyRequest) String() string { func (*ApplyRequest) ProtoMessage() {} func (x *ApplyRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[20] + mi := &file_api_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1499,7 +1883,7 @@ func (x *ApplyRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyRequest.ProtoReflect.Descriptor instead. func (*ApplyRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{20} + return file_api_proto_rawDescGZIP(), []int{27} } func (x *ApplyRequest) GetResource() *Resource { @@ -1537,7 +1921,7 @@ type ApplyResponse struct { func (x *ApplyResponse) Reset() { *x = ApplyResponse{} - mi := &file_api_proto_msgTypes[21] + mi := &file_api_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1549,7 +1933,7 @@ func (x *ApplyResponse) String() string { func (*ApplyResponse) ProtoMessage() {} func (x *ApplyResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[21] + mi := &file_api_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1562,7 +1946,7 @@ func (x *ApplyResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyResponse.ProtoReflect.Descriptor instead. func (*ApplyResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{21} + return file_api_proto_rawDescGZIP(), []int{28} } func (x *ApplyResponse) GetOperationId() string { @@ -1590,7 +1974,7 @@ type GetRequest struct { func (x *GetRequest) Reset() { *x = GetRequest{} - mi := &file_api_proto_msgTypes[22] + mi := &file_api_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1602,7 +1986,7 @@ func (x *GetRequest) String() string { func (*GetRequest) ProtoMessage() {} func (x *GetRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[22] + mi := &file_api_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1615,7 +1999,7 @@ func (x *GetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRequest.ProtoReflect.Descriptor instead. func (*GetRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{22} + return file_api_proto_rawDescGZIP(), []int{29} } func (x *GetRequest) GetApiVersion() string { @@ -1658,7 +2042,7 @@ type GetResponse struct { func (x *GetResponse) Reset() { *x = GetResponse{} - mi := &file_api_proto_msgTypes[23] + mi := &file_api_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1670,7 +2054,7 @@ func (x *GetResponse) String() string { func (*GetResponse) ProtoMessage() {} func (x *GetResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[23] + mi := &file_api_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1683,7 +2067,7 @@ func (x *GetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetResponse.ProtoReflect.Descriptor instead. func (*GetResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{23} + return file_api_proto_rawDescGZIP(), []int{30} } func (x *GetResponse) GetResource() *Resource { @@ -1717,7 +2101,7 @@ type ListRequest struct { func (x *ListRequest) Reset() { *x = ListRequest{} - mi := &file_api_proto_msgTypes[24] + mi := &file_api_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1729,7 +2113,7 @@ func (x *ListRequest) String() string { func (*ListRequest) ProtoMessage() {} func (x *ListRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[24] + mi := &file_api_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1742,7 +2126,7 @@ func (x *ListRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRequest.ProtoReflect.Descriptor instead. func (*ListRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{24} + return file_api_proto_rawDescGZIP(), []int{31} } func (x *ListRequest) GetApiVersion() string { @@ -1768,7 +2152,7 @@ type ListResponse struct { func (x *ListResponse) Reset() { *x = ListResponse{} - mi := &file_api_proto_msgTypes[25] + mi := &file_api_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1780,7 +2164,7 @@ func (x *ListResponse) String() string { func (*ListResponse) ProtoMessage() {} func (x *ListResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[25] + mi := &file_api_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1793,7 +2177,7 @@ func (x *ListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListResponse.ProtoReflect.Descriptor instead. func (*ListResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{25} + return file_api_proto_rawDescGZIP(), []int{32} } func (x *ListResponse) GetResources() []*Resource { @@ -1814,7 +2198,7 @@ type DeleteRequest struct { func (x *DeleteRequest) Reset() { *x = DeleteRequest{} - mi := &file_api_proto_msgTypes[26] + mi := &file_api_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1826,7 +2210,7 @@ func (x *DeleteRequest) String() string { func (*DeleteRequest) ProtoMessage() {} func (x *DeleteRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[26] + mi := &file_api_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1839,7 +2223,7 @@ func (x *DeleteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteRequest.ProtoReflect.Descriptor instead. func (*DeleteRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{26} + return file_api_proto_rawDescGZIP(), []int{33} } func (x *DeleteRequest) GetApiVersion() string { @@ -1874,7 +2258,7 @@ type DeleteResponse struct { func (x *DeleteResponse) Reset() { *x = DeleteResponse{} - mi := &file_api_proto_msgTypes[27] + mi := &file_api_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1886,7 +2270,7 @@ func (x *DeleteResponse) String() string { func (*DeleteResponse) ProtoMessage() {} func (x *DeleteResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[27] + mi := &file_api_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1899,7 +2283,7 @@ func (x *DeleteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteResponse.ProtoReflect.Descriptor instead. func (*DeleteResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{27} + return file_api_proto_rawDescGZIP(), []int{34} } func (x *DeleteResponse) GetOperationId() string { @@ -1930,7 +2314,7 @@ type WatchRequest struct { func (x *WatchRequest) Reset() { *x = WatchRequest{} - mi := &file_api_proto_msgTypes[28] + mi := &file_api_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1942,7 +2326,7 @@ func (x *WatchRequest) String() string { func (*WatchRequest) ProtoMessage() {} func (x *WatchRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[28] + mi := &file_api_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1955,7 +2339,7 @@ func (x *WatchRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchRequest.ProtoReflect.Descriptor instead. func (*WatchRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{28} + return file_api_proto_rawDescGZIP(), []int{35} } func (x *WatchRequest) GetApiVersion() string { @@ -1992,7 +2376,7 @@ type WatchEvent struct { func (x *WatchEvent) Reset() { *x = WatchEvent{} - mi := &file_api_proto_msgTypes[29] + mi := &file_api_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2004,7 +2388,7 @@ func (x *WatchEvent) String() string { func (*WatchEvent) ProtoMessage() {} func (x *WatchEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[29] + mi := &file_api_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2017,7 +2401,7 @@ func (x *WatchEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchEvent.ProtoReflect.Descriptor instead. func (*WatchEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{29} + return file_api_proto_rawDescGZIP(), []int{36} } func (x *WatchEvent) GetType() WatchEvent_Type { @@ -2042,7 +2426,7 @@ type ListProvidersRequest struct { func (x *ListProvidersRequest) Reset() { *x = ListProvidersRequest{} - mi := &file_api_proto_msgTypes[30] + mi := &file_api_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2054,7 +2438,7 @@ func (x *ListProvidersRequest) String() string { func (*ListProvidersRequest) ProtoMessage() {} func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[30] + mi := &file_api_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2067,7 +2451,7 @@ func (x *ListProvidersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProvidersRequest.ProtoReflect.Descriptor instead. func (*ListProvidersRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{30} + return file_api_proto_rawDescGZIP(), []int{37} } type ListProvidersResponse struct { @@ -2079,7 +2463,7 @@ type ListProvidersResponse struct { func (x *ListProvidersResponse) Reset() { *x = ListProvidersResponse{} - mi := &file_api_proto_msgTypes[31] + mi := &file_api_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2091,7 +2475,7 @@ func (x *ListProvidersResponse) String() string { func (*ListProvidersResponse) ProtoMessage() {} func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[31] + mi := &file_api_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2104,7 +2488,7 @@ func (x *ListProvidersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListProvidersResponse.ProtoReflect.Descriptor instead. func (*ListProvidersResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{31} + return file_api_proto_rawDescGZIP(), []int{38} } func (x *ListProvidersResponse) GetProviders() []*ProviderEntry { @@ -2133,7 +2517,7 @@ type ProviderEntry struct { func (x *ProviderEntry) Reset() { *x = ProviderEntry{} - mi := &file_api_proto_msgTypes[32] + mi := &file_api_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2145,7 +2529,7 @@ func (x *ProviderEntry) String() string { func (*ProviderEntry) ProtoMessage() {} func (x *ProviderEntry) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[32] + mi := &file_api_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2158,7 +2542,7 @@ func (x *ProviderEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use ProviderEntry.ProtoReflect.Descriptor instead. func (*ProviderEntry) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{32} + return file_api_proto_rawDescGZIP(), []int{39} } func (x *ProviderEntry) GetName() string { @@ -2211,7 +2595,7 @@ type UpsertProviderRequest struct { func (x *UpsertProviderRequest) Reset() { *x = UpsertProviderRequest{} - mi := &file_api_proto_msgTypes[33] + mi := &file_api_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2223,7 +2607,7 @@ func (x *UpsertProviderRequest) String() string { func (*UpsertProviderRequest) ProtoMessage() {} func (x *UpsertProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[33] + mi := &file_api_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2236,7 +2620,7 @@ func (x *UpsertProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertProviderRequest.ProtoReflect.Descriptor instead. func (*UpsertProviderRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{33} + return file_api_proto_rawDescGZIP(), []int{40} } func (x *UpsertProviderRequest) GetName() string { @@ -2276,7 +2660,7 @@ type UpsertProviderResponse struct { func (x *UpsertProviderResponse) Reset() { *x = UpsertProviderResponse{} - mi := &file_api_proto_msgTypes[34] + mi := &file_api_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2288,7 +2672,7 @@ func (x *UpsertProviderResponse) String() string { func (*UpsertProviderResponse) ProtoMessage() {} func (x *UpsertProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[34] + mi := &file_api_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2301,7 +2685,7 @@ func (x *UpsertProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertProviderResponse.ProtoReflect.Descriptor instead. func (*UpsertProviderResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{34} + return file_api_proto_rawDescGZIP(), []int{41} } func (x *UpsertProviderResponse) GetProvider() *ProviderEntry { @@ -2320,7 +2704,7 @@ type DeleteProviderRequest struct { func (x *DeleteProviderRequest) Reset() { *x = DeleteProviderRequest{} - mi := &file_api_proto_msgTypes[35] + mi := &file_api_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2332,7 +2716,7 @@ func (x *DeleteProviderRequest) String() string { func (*DeleteProviderRequest) ProtoMessage() {} func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[35] + mi := &file_api_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2345,7 +2729,7 @@ func (x *DeleteProviderRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderRequest.ProtoReflect.Descriptor instead. func (*DeleteProviderRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{35} + return file_api_proto_rawDescGZIP(), []int{42} } func (x *DeleteProviderRequest) GetName() string { @@ -2363,7 +2747,7 @@ type DeleteProviderResponse struct { func (x *DeleteProviderResponse) Reset() { *x = DeleteProviderResponse{} - mi := &file_api_proto_msgTypes[36] + mi := &file_api_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2375,7 +2759,7 @@ func (x *DeleteProviderResponse) String() string { func (*DeleteProviderResponse) ProtoMessage() {} func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[36] + mi := &file_api_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2388,7 +2772,7 @@ func (x *DeleteProviderResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteProviderResponse.ProtoReflect.Descriptor instead. func (*DeleteProviderResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{36} + return file_api_proto_rawDescGZIP(), []int{43} } // ControllerConfig carries the editable controller-behavior tunables. All @@ -2410,7 +2794,7 @@ type ControllerConfig struct { func (x *ControllerConfig) Reset() { *x = ControllerConfig{} - mi := &file_api_proto_msgTypes[37] + mi := &file_api_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2422,7 +2806,7 @@ func (x *ControllerConfig) String() string { func (*ControllerConfig) ProtoMessage() {} func (x *ControllerConfig) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[37] + mi := &file_api_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2435,7 +2819,7 @@ func (x *ControllerConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use ControllerConfig.ProtoReflect.Descriptor instead. func (*ControllerConfig) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{37} + return file_api_proto_rawDescGZIP(), []int{44} } func (x *ControllerConfig) GetReconcilerEnabled() bool { @@ -2467,7 +2851,7 @@ type GetControllerConfigRequest struct { func (x *GetControllerConfigRequest) Reset() { *x = GetControllerConfigRequest{} - mi := &file_api_proto_msgTypes[38] + mi := &file_api_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2479,7 +2863,7 @@ func (x *GetControllerConfigRequest) String() string { func (*GetControllerConfigRequest) ProtoMessage() {} func (x *GetControllerConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[38] + mi := &file_api_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2492,7 +2876,7 @@ func (x *GetControllerConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetControllerConfigRequest.ProtoReflect.Descriptor instead. func (*GetControllerConfigRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{38} + return file_api_proto_rawDescGZIP(), []int{45} } type GetControllerConfigResponse struct { @@ -2507,7 +2891,7 @@ type GetControllerConfigResponse struct { func (x *GetControllerConfigResponse) Reset() { *x = GetControllerConfigResponse{} - mi := &file_api_proto_msgTypes[39] + mi := &file_api_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2519,7 +2903,7 @@ func (x *GetControllerConfigResponse) String() string { func (*GetControllerConfigResponse) ProtoMessage() {} func (x *GetControllerConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[39] + mi := &file_api_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2532,7 +2916,7 @@ func (x *GetControllerConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetControllerConfigResponse.ProtoReflect.Descriptor instead. func (*GetControllerConfigResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{39} + return file_api_proto_rawDescGZIP(), []int{46} } func (x *GetControllerConfigResponse) GetConfig() *ControllerConfig { @@ -2558,7 +2942,7 @@ type UpdateControllerConfigRequest struct { func (x *UpdateControllerConfigRequest) Reset() { *x = UpdateControllerConfigRequest{} - mi := &file_api_proto_msgTypes[40] + mi := &file_api_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2570,7 +2954,7 @@ func (x *UpdateControllerConfigRequest) String() string { func (*UpdateControllerConfigRequest) ProtoMessage() {} func (x *UpdateControllerConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[40] + mi := &file_api_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2583,7 +2967,7 @@ func (x *UpdateControllerConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateControllerConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateControllerConfigRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{40} + return file_api_proto_rawDescGZIP(), []int{47} } func (x *UpdateControllerConfigRequest) GetConfig() *ControllerConfig { @@ -2603,7 +2987,7 @@ type UpdateControllerConfigResponse struct { func (x *UpdateControllerConfigResponse) Reset() { *x = UpdateControllerConfigResponse{} - mi := &file_api_proto_msgTypes[41] + mi := &file_api_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2615,7 +2999,7 @@ func (x *UpdateControllerConfigResponse) String() string { func (*UpdateControllerConfigResponse) ProtoMessage() {} func (x *UpdateControllerConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[41] + mi := &file_api_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2628,7 +3012,7 @@ func (x *UpdateControllerConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateControllerConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateControllerConfigResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{41} + return file_api_proto_rawDescGZIP(), []int{48} } func (x *UpdateControllerConfigResponse) GetConfig() *ControllerConfig { @@ -2653,7 +3037,7 @@ type ListTemplatesRequest struct { func (x *ListTemplatesRequest) Reset() { *x = ListTemplatesRequest{} - mi := &file_api_proto_msgTypes[42] + mi := &file_api_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2665,7 +3049,7 @@ func (x *ListTemplatesRequest) String() string { func (*ListTemplatesRequest) ProtoMessage() {} func (x *ListTemplatesRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[42] + mi := &file_api_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2678,7 +3062,7 @@ func (x *ListTemplatesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTemplatesRequest.ProtoReflect.Descriptor instead. func (*ListTemplatesRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{42} + return file_api_proto_rawDescGZIP(), []int{49} } type ListTemplatesResponse struct { @@ -2690,7 +3074,7 @@ type ListTemplatesResponse struct { func (x *ListTemplatesResponse) Reset() { *x = ListTemplatesResponse{} - mi := &file_api_proto_msgTypes[43] + mi := &file_api_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2702,7 +3086,7 @@ func (x *ListTemplatesResponse) String() string { func (*ListTemplatesResponse) ProtoMessage() {} func (x *ListTemplatesResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[43] + mi := &file_api_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2715,7 +3099,7 @@ func (x *ListTemplatesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTemplatesResponse.ProtoReflect.Descriptor instead. func (*ListTemplatesResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{43} + return file_api_proto_rawDescGZIP(), []int{50} } func (x *ListTemplatesResponse) GetTemplates() []*TemplateSummary { @@ -2740,7 +3124,7 @@ type TemplateSummary struct { func (x *TemplateSummary) Reset() { *x = TemplateSummary{} - mi := &file_api_proto_msgTypes[44] + mi := &file_api_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2752,7 +3136,7 @@ func (x *TemplateSummary) String() string { func (*TemplateSummary) ProtoMessage() {} func (x *TemplateSummary) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[44] + mi := &file_api_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2765,7 +3149,7 @@ func (x *TemplateSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use TemplateSummary.ProtoReflect.Descriptor instead. func (*TemplateSummary) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{44} + return file_api_proto_rawDescGZIP(), []int{51} } func (x *TemplateSummary) GetName() string { @@ -2812,7 +3196,7 @@ type GetTemplateRequest struct { func (x *GetTemplateRequest) Reset() { *x = GetTemplateRequest{} - mi := &file_api_proto_msgTypes[45] + mi := &file_api_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2824,7 +3208,7 @@ func (x *GetTemplateRequest) String() string { func (*GetTemplateRequest) ProtoMessage() {} func (x *GetTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[45] + mi := &file_api_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2837,7 +3221,7 @@ func (x *GetTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTemplateRequest.ProtoReflect.Descriptor instead. func (*GetTemplateRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{45} + return file_api_proto_rawDescGZIP(), []int{52} } func (x *GetTemplateRequest) GetName() string { @@ -2857,7 +3241,7 @@ type GetTemplateResponse struct { func (x *GetTemplateResponse) Reset() { *x = GetTemplateResponse{} - mi := &file_api_proto_msgTypes[46] + mi := &file_api_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2869,7 +3253,7 @@ func (x *GetTemplateResponse) String() string { func (*GetTemplateResponse) ProtoMessage() {} func (x *GetTemplateResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[46] + mi := &file_api_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2882,7 +3266,7 @@ func (x *GetTemplateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetTemplateResponse.ProtoReflect.Descriptor instead. func (*GetTemplateResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{46} + return file_api_proto_rawDescGZIP(), []int{53} } func (x *GetTemplateResponse) GetSummary() *TemplateSummary { @@ -2922,7 +3306,7 @@ type TemplateParameter struct { func (x *TemplateParameter) Reset() { *x = TemplateParameter{} - mi := &file_api_proto_msgTypes[47] + mi := &file_api_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2934,7 +3318,7 @@ func (x *TemplateParameter) String() string { func (*TemplateParameter) ProtoMessage() {} func (x *TemplateParameter) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[47] + mi := &file_api_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2947,7 +3331,7 @@ func (x *TemplateParameter) ProtoReflect() protoreflect.Message { // Deprecated: Use TemplateParameter.ProtoReflect.Descriptor instead. func (*TemplateParameter) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{47} + return file_api_proto_rawDescGZIP(), []int{54} } func (x *TemplateParameter) GetName() string { @@ -3011,7 +3395,7 @@ type RenderTemplateRequest struct { func (x *RenderTemplateRequest) Reset() { *x = RenderTemplateRequest{} - mi := &file_api_proto_msgTypes[48] + mi := &file_api_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3023,7 +3407,7 @@ func (x *RenderTemplateRequest) String() string { func (*RenderTemplateRequest) ProtoMessage() {} func (x *RenderTemplateRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[48] + mi := &file_api_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3036,7 +3420,7 @@ func (x *RenderTemplateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RenderTemplateRequest.ProtoReflect.Descriptor instead. func (*RenderTemplateRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{48} + return file_api_proto_rawDescGZIP(), []int{55} } func (x *RenderTemplateRequest) GetName() string { @@ -3062,7 +3446,7 @@ type RenderTemplateResponse struct { func (x *RenderTemplateResponse) Reset() { *x = RenderTemplateResponse{} - mi := &file_api_proto_msgTypes[49] + mi := &file_api_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3074,7 +3458,7 @@ func (x *RenderTemplateResponse) String() string { func (*RenderTemplateResponse) ProtoMessage() {} func (x *RenderTemplateResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[49] + mi := &file_api_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3087,7 +3471,7 @@ func (x *RenderTemplateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RenderTemplateResponse.ProtoReflect.Descriptor instead. func (*RenderTemplateResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{49} + return file_api_proto_rawDescGZIP(), []int{56} } func (x *RenderTemplateResponse) GetResource() *Resource { @@ -3132,7 +3516,7 @@ type Operation struct { func (x *Operation) Reset() { *x = Operation{} - mi := &file_api_proto_msgTypes[50] + mi := &file_api_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3144,7 +3528,7 @@ func (x *Operation) String() string { func (*Operation) ProtoMessage() {} func (x *Operation) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[50] + mi := &file_api_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3157,7 +3541,7 @@ func (x *Operation) ProtoReflect() protoreflect.Message { // Deprecated: Use Operation.ProtoReflect.Descriptor instead. func (*Operation) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{50} + return file_api_proto_rawDescGZIP(), []int{57} } func (x *Operation) GetId() string { @@ -3284,7 +3668,7 @@ type GetOperationRequest struct { func (x *GetOperationRequest) Reset() { *x = GetOperationRequest{} - mi := &file_api_proto_msgTypes[51] + mi := &file_api_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3296,7 +3680,7 @@ func (x *GetOperationRequest) String() string { func (*GetOperationRequest) ProtoMessage() {} func (x *GetOperationRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[51] + mi := &file_api_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3309,7 +3693,7 @@ func (x *GetOperationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetOperationRequest.ProtoReflect.Descriptor instead. func (*GetOperationRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{51} + return file_api_proto_rawDescGZIP(), []int{58} } func (x *GetOperationRequest) GetId() string { @@ -3347,7 +3731,7 @@ type ListOperationsRequest struct { func (x *ListOperationsRequest) Reset() { *x = ListOperationsRequest{} - mi := &file_api_proto_msgTypes[52] + mi := &file_api_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3359,7 +3743,7 @@ func (x *ListOperationsRequest) String() string { func (*ListOperationsRequest) ProtoMessage() {} func (x *ListOperationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[52] + mi := &file_api_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3372,7 +3756,7 @@ func (x *ListOperationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListOperationsRequest.ProtoReflect.Descriptor instead. func (*ListOperationsRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{52} + return file_api_proto_rawDescGZIP(), []int{59} } func (x *ListOperationsRequest) GetStatus() string { @@ -3440,7 +3824,7 @@ type ListOperationsResponse struct { func (x *ListOperationsResponse) Reset() { *x = ListOperationsResponse{} - mi := &file_api_proto_msgTypes[53] + mi := &file_api_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3452,7 +3836,7 @@ func (x *ListOperationsResponse) String() string { func (*ListOperationsResponse) ProtoMessage() {} func (x *ListOperationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[53] + mi := &file_api_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3465,7 +3849,7 @@ func (x *ListOperationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListOperationsResponse.ProtoReflect.Descriptor instead. func (*ListOperationsResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{53} + return file_api_proto_rawDescGZIP(), []int{60} } func (x *ListOperationsResponse) GetOperations() []*Operation { @@ -3484,7 +3868,7 @@ type CancelOperationRequest struct { func (x *CancelOperationRequest) Reset() { *x = CancelOperationRequest{} - mi := &file_api_proto_msgTypes[54] + mi := &file_api_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3496,7 +3880,7 @@ func (x *CancelOperationRequest) String() string { func (*CancelOperationRequest) ProtoMessage() {} func (x *CancelOperationRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[54] + mi := &file_api_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3509,7 +3893,7 @@ func (x *CancelOperationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelOperationRequest.ProtoReflect.Descriptor instead. func (*CancelOperationRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{54} + return file_api_proto_rawDescGZIP(), []int{61} } func (x *CancelOperationRequest) GetId() string { @@ -3531,7 +3915,7 @@ type CancelOperationResponse struct { func (x *CancelOperationResponse) Reset() { *x = CancelOperationResponse{} - mi := &file_api_proto_msgTypes[55] + mi := &file_api_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3543,7 +3927,7 @@ func (x *CancelOperationResponse) String() string { func (*CancelOperationResponse) ProtoMessage() {} func (x *CancelOperationResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[55] + mi := &file_api_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3556,7 +3940,7 @@ func (x *CancelOperationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelOperationResponse.ProtoReflect.Descriptor instead. func (*CancelOperationResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{55} + return file_api_proto_rawDescGZIP(), []int{62} } func (x *CancelOperationResponse) GetStatus() string { @@ -3587,7 +3971,7 @@ type LoginRequest struct { func (x *LoginRequest) Reset() { *x = LoginRequest{} - mi := &file_api_proto_msgTypes[56] + mi := &file_api_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3599,7 +3983,7 @@ func (x *LoginRequest) String() string { func (*LoginRequest) ProtoMessage() {} func (x *LoginRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[56] + mi := &file_api_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3612,7 +3996,7 @@ func (x *LoginRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginRequest.ProtoReflect.Descriptor instead. func (*LoginRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{56} + return file_api_proto_rawDescGZIP(), []int{63} } func (x *LoginRequest) GetDisplayName() string { @@ -3642,7 +4026,7 @@ type LoginResponse struct { func (x *LoginResponse) Reset() { *x = LoginResponse{} - mi := &file_api_proto_msgTypes[57] + mi := &file_api_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3654,7 +4038,7 @@ func (x *LoginResponse) String() string { func (*LoginResponse) ProtoMessage() {} func (x *LoginResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[57] + mi := &file_api_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3667,7 +4051,7 @@ func (x *LoginResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LoginResponse.ProtoReflect.Descriptor instead. func (*LoginResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{57} + return file_api_proto_rawDescGZIP(), []int{64} } func (x *LoginResponse) GetToken() string { @@ -3699,7 +4083,7 @@ type LogoutRequest struct { func (x *LogoutRequest) Reset() { *x = LogoutRequest{} - mi := &file_api_proto_msgTypes[58] + mi := &file_api_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3711,7 +4095,7 @@ func (x *LogoutRequest) String() string { func (*LogoutRequest) ProtoMessage() {} func (x *LogoutRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[58] + mi := &file_api_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3724,7 +4108,7 @@ func (x *LogoutRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LogoutRequest.ProtoReflect.Descriptor instead. func (*LogoutRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{58} + return file_api_proto_rawDescGZIP(), []int{65} } type LogoutResponse struct { @@ -3735,7 +4119,7 @@ type LogoutResponse struct { func (x *LogoutResponse) Reset() { *x = LogoutResponse{} - mi := &file_api_proto_msgTypes[59] + mi := &file_api_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3747,7 +4131,7 @@ func (x *LogoutResponse) String() string { func (*LogoutResponse) ProtoMessage() {} func (x *LogoutResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[59] + mi := &file_api_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3760,7 +4144,7 @@ func (x *LogoutResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LogoutResponse.ProtoReflect.Descriptor instead. func (*LogoutResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{59} + return file_api_proto_rawDescGZIP(), []int{66} } type WhoAmIRequest struct { @@ -3771,7 +4155,7 @@ type WhoAmIRequest struct { func (x *WhoAmIRequest) Reset() { *x = WhoAmIRequest{} - mi := &file_api_proto_msgTypes[60] + mi := &file_api_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3783,7 +4167,7 @@ func (x *WhoAmIRequest) String() string { func (*WhoAmIRequest) ProtoMessage() {} func (x *WhoAmIRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[60] + mi := &file_api_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3796,7 +4180,7 @@ func (x *WhoAmIRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WhoAmIRequest.ProtoReflect.Descriptor instead. func (*WhoAmIRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{60} + return file_api_proto_rawDescGZIP(), []int{67} } // WhoAmIResponse returns identifying info about the auth credential used @@ -3814,7 +4198,7 @@ type WhoAmIResponse struct { func (x *WhoAmIResponse) Reset() { *x = WhoAmIResponse{} - mi := &file_api_proto_msgTypes[61] + mi := &file_api_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3826,7 +4210,7 @@ func (x *WhoAmIResponse) String() string { func (*WhoAmIResponse) ProtoMessage() {} func (x *WhoAmIResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[61] + mi := &file_api_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3839,7 +4223,7 @@ func (x *WhoAmIResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WhoAmIResponse.ProtoReflect.Descriptor instead. func (*WhoAmIResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{61} + return file_api_proto_rawDescGZIP(), []int{68} } func (x *WhoAmIResponse) GetUserId() string { @@ -3871,7 +4255,7 @@ type ListSchemasRequest struct { func (x *ListSchemasRequest) Reset() { *x = ListSchemasRequest{} - mi := &file_api_proto_msgTypes[62] + mi := &file_api_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3883,7 +4267,7 @@ func (x *ListSchemasRequest) String() string { func (*ListSchemasRequest) ProtoMessage() {} func (x *ListSchemasRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[62] + mi := &file_api_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3896,7 +4280,7 @@ func (x *ListSchemasRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSchemasRequest.ProtoReflect.Descriptor instead. func (*ListSchemasRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{62} + return file_api_proto_rawDescGZIP(), []int{69} } type ListSchemasResponse struct { @@ -3908,7 +4292,7 @@ type ListSchemasResponse struct { func (x *ListSchemasResponse) Reset() { *x = ListSchemasResponse{} - mi := &file_api_proto_msgTypes[63] + mi := &file_api_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3920,7 +4304,7 @@ func (x *ListSchemasResponse) String() string { func (*ListSchemasResponse) ProtoMessage() {} func (x *ListSchemasResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[63] + mi := &file_api_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3933,7 +4317,7 @@ func (x *ListSchemasResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSchemasResponse.ProtoReflect.Descriptor instead. func (*ListSchemasResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{63} + return file_api_proto_rawDescGZIP(), []int{70} } func (x *ListSchemasResponse) GetSchemas() []*SchemaInfo { @@ -3968,7 +4352,7 @@ type SchemaInfo struct { func (x *SchemaInfo) Reset() { *x = SchemaInfo{} - mi := &file_api_proto_msgTypes[64] + mi := &file_api_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3980,7 +4364,7 @@ func (x *SchemaInfo) String() string { func (*SchemaInfo) ProtoMessage() {} func (x *SchemaInfo) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[64] + mi := &file_api_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3993,7 +4377,7 @@ func (x *SchemaInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use SchemaInfo.ProtoReflect.Descriptor instead. func (*SchemaInfo) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{64} + return file_api_proto_rawDescGZIP(), []int{71} } func (x *SchemaInfo) GetApiVersion() string { @@ -4055,7 +4439,7 @@ type GetSchemaRequest struct { func (x *GetSchemaRequest) Reset() { *x = GetSchemaRequest{} - mi := &file_api_proto_msgTypes[65] + mi := &file_api_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4067,7 +4451,7 @@ func (x *GetSchemaRequest) String() string { func (*GetSchemaRequest) ProtoMessage() {} func (x *GetSchemaRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[65] + mi := &file_api_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4080,7 +4464,7 @@ func (x *GetSchemaRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSchemaRequest.ProtoReflect.Descriptor instead. func (*GetSchemaRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{65} + return file_api_proto_rawDescGZIP(), []int{72} } func (x *GetSchemaRequest) GetApiVersion() string { @@ -4109,7 +4493,7 @@ type GetSchemaResponse struct { func (x *GetSchemaResponse) Reset() { *x = GetSchemaResponse{} - mi := &file_api_proto_msgTypes[66] + mi := &file_api_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4121,7 +4505,7 @@ func (x *GetSchemaResponse) String() string { func (*GetSchemaResponse) ProtoMessage() {} func (x *GetSchemaResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[66] + mi := &file_api_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4134,7 +4518,7 @@ func (x *GetSchemaResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSchemaResponse.ProtoReflect.Descriptor instead. func (*GetSchemaResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{66} + return file_api_proto_rawDescGZIP(), []int{73} } func (x *GetSchemaResponse) GetInfo() *SchemaInfo { @@ -4160,7 +4544,7 @@ type ValidateRequest struct { func (x *ValidateRequest) Reset() { *x = ValidateRequest{} - mi := &file_api_proto_msgTypes[67] + mi := &file_api_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4172,7 +4556,7 @@ func (x *ValidateRequest) String() string { func (*ValidateRequest) ProtoMessage() {} func (x *ValidateRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[67] + mi := &file_api_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4185,7 +4569,7 @@ func (x *ValidateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ValidateRequest.ProtoReflect.Descriptor instead. func (*ValidateRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{67} + return file_api_proto_rawDescGZIP(), []int{74} } func (x *ValidateRequest) GetResource() *Resource { @@ -4207,7 +4591,7 @@ type ValidateResponse struct { func (x *ValidateResponse) Reset() { *x = ValidateResponse{} - mi := &file_api_proto_msgTypes[68] + mi := &file_api_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4219,7 +4603,7 @@ func (x *ValidateResponse) String() string { func (*ValidateResponse) ProtoMessage() {} func (x *ValidateResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[68] + mi := &file_api_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4232,7 +4616,7 @@ func (x *ValidateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ValidateResponse.ProtoReflect.Descriptor instead. func (*ValidateResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{68} + return file_api_proto_rawDescGZIP(), []int{75} } func (x *ValidateResponse) GetErrors() []string { @@ -4252,7 +4636,7 @@ type GetFormSchemaRequest struct { func (x *GetFormSchemaRequest) Reset() { *x = GetFormSchemaRequest{} - mi := &file_api_proto_msgTypes[69] + mi := &file_api_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4264,7 +4648,7 @@ func (x *GetFormSchemaRequest) String() string { func (*GetFormSchemaRequest) ProtoMessage() {} func (x *GetFormSchemaRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[69] + mi := &file_api_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4277,7 +4661,7 @@ func (x *GetFormSchemaRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFormSchemaRequest.ProtoReflect.Descriptor instead. func (*GetFormSchemaRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{69} + return file_api_proto_rawDescGZIP(), []int{76} } func (x *GetFormSchemaRequest) GetApiVersion() string { @@ -4310,7 +4694,7 @@ type GetFormSchemaResponse struct { func (x *GetFormSchemaResponse) Reset() { *x = GetFormSchemaResponse{} - mi := &file_api_proto_msgTypes[70] + mi := &file_api_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4322,7 +4706,7 @@ func (x *GetFormSchemaResponse) String() string { func (*GetFormSchemaResponse) ProtoMessage() {} func (x *GetFormSchemaResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[70] + mi := &file_api_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4335,7 +4719,7 @@ func (x *GetFormSchemaResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFormSchemaResponse.ProtoReflect.Descriptor instead. func (*GetFormSchemaResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{70} + return file_api_proto_rawDescGZIP(), []int{77} } func (x *GetFormSchemaResponse) GetJson() string { @@ -4358,7 +4742,7 @@ type WatchOperationsRequest struct { func (x *WatchOperationsRequest) Reset() { *x = WatchOperationsRequest{} - mi := &file_api_proto_msgTypes[71] + mi := &file_api_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4370,7 +4754,7 @@ func (x *WatchOperationsRequest) String() string { func (*WatchOperationsRequest) ProtoMessage() {} func (x *WatchOperationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[71] + mi := &file_api_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4383,7 +4767,7 @@ func (x *WatchOperationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchOperationsRequest.ProtoReflect.Descriptor instead. func (*WatchOperationsRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{71} + return file_api_proto_rawDescGZIP(), []int{78} } func (x *WatchOperationsRequest) GetId() string { @@ -4436,7 +4820,7 @@ type OperationEvent struct { func (x *OperationEvent) Reset() { *x = OperationEvent{} - mi := &file_api_proto_msgTypes[72] + mi := &file_api_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4448,7 +4832,7 @@ func (x *OperationEvent) String() string { func (*OperationEvent) ProtoMessage() {} func (x *OperationEvent) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[72] + mi := &file_api_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4461,7 +4845,7 @@ func (x *OperationEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use OperationEvent.ProtoReflect.Descriptor instead. func (*OperationEvent) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{72} + return file_api_proto_rawDescGZIP(), []int{79} } func (x *OperationEvent) GetOperation() *Operation { @@ -4486,7 +4870,7 @@ type GetRepoStatusRequest struct { func (x *GetRepoStatusRequest) Reset() { *x = GetRepoStatusRequest{} - mi := &file_api_proto_msgTypes[73] + mi := &file_api_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4498,7 +4882,7 @@ func (x *GetRepoStatusRequest) String() string { func (*GetRepoStatusRequest) ProtoMessage() {} func (x *GetRepoStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[73] + mi := &file_api_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4511,7 +4895,7 @@ func (x *GetRepoStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRepoStatusRequest.ProtoReflect.Descriptor instead. func (*GetRepoStatusRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{73} + return file_api_proto_rawDescGZIP(), []int{80} } // GetRepoStatusResponse is a snapshot of the manifest dir's git status. @@ -4537,7 +4921,7 @@ type GetRepoStatusResponse struct { func (x *GetRepoStatusResponse) Reset() { *x = GetRepoStatusResponse{} - mi := &file_api_proto_msgTypes[74] + mi := &file_api_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4549,7 +4933,7 @@ func (x *GetRepoStatusResponse) String() string { func (*GetRepoStatusResponse) ProtoMessage() {} func (x *GetRepoStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[74] + mi := &file_api_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4562,7 +4946,7 @@ func (x *GetRepoStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRepoStatusResponse.ProtoReflect.Descriptor instead. func (*GetRepoStatusResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{74} + return file_api_proto_rawDescGZIP(), []int{81} } func (x *GetRepoStatusResponse) GetEnabled() bool { @@ -4643,7 +5027,7 @@ type PushRepoRequest struct { func (x *PushRepoRequest) Reset() { *x = PushRepoRequest{} - mi := &file_api_proto_msgTypes[75] + mi := &file_api_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4655,7 +5039,7 @@ func (x *PushRepoRequest) String() string { func (*PushRepoRequest) ProtoMessage() {} func (x *PushRepoRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[75] + mi := &file_api_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4668,7 +5052,7 @@ func (x *PushRepoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PushRepoRequest.ProtoReflect.Descriptor instead. func (*PushRepoRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{75} + return file_api_proto_rawDescGZIP(), []int{82} } type PushRepoResponse struct { @@ -4680,7 +5064,7 @@ type PushRepoResponse struct { func (x *PushRepoResponse) Reset() { *x = PushRepoResponse{} - mi := &file_api_proto_msgTypes[76] + mi := &file_api_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4692,7 +5076,7 @@ func (x *PushRepoResponse) String() string { func (*PushRepoResponse) ProtoMessage() {} func (x *PushRepoResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[76] + mi := &file_api_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4705,7 +5089,7 @@ func (x *PushRepoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PushRepoResponse.ProtoReflect.Descriptor instead. func (*PushRepoResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{76} + return file_api_proto_rawDescGZIP(), []int{83} } func (x *PushRepoResponse) GetMessage() string { @@ -4723,7 +5107,7 @@ type PullRepoRequest struct { func (x *PullRepoRequest) Reset() { *x = PullRepoRequest{} - mi := &file_api_proto_msgTypes[77] + mi := &file_api_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4735,7 +5119,7 @@ func (x *PullRepoRequest) String() string { func (*PullRepoRequest) ProtoMessage() {} func (x *PullRepoRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[77] + mi := &file_api_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4748,7 +5132,7 @@ func (x *PullRepoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PullRepoRequest.ProtoReflect.Descriptor instead. func (*PullRepoRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{77} + return file_api_proto_rawDescGZIP(), []int{84} } type PullRepoResponse struct { @@ -4760,7 +5144,7 @@ type PullRepoResponse struct { func (x *PullRepoResponse) Reset() { *x = PullRepoResponse{} - mi := &file_api_proto_msgTypes[78] + mi := &file_api_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4772,7 +5156,7 @@ func (x *PullRepoResponse) String() string { func (*PullRepoResponse) ProtoMessage() {} func (x *PullRepoResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[78] + mi := &file_api_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4785,7 +5169,7 @@ func (x *PullRepoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PullRepoResponse.ProtoReflect.Descriptor instead. func (*PullRepoResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{78} + return file_api_proto_rawDescGZIP(), []int{85} } func (x *PullRepoResponse) GetMessage() string { @@ -4808,7 +5192,7 @@ type CommitInfo struct { func (x *CommitInfo) Reset() { *x = CommitInfo{} - mi := &file_api_proto_msgTypes[79] + mi := &file_api_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4820,7 +5204,7 @@ func (x *CommitInfo) String() string { func (*CommitInfo) ProtoMessage() {} func (x *CommitInfo) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[79] + mi := &file_api_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4833,7 +5217,7 @@ func (x *CommitInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use CommitInfo.ProtoReflect.Descriptor instead. func (*CommitInfo) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{79} + return file_api_proto_rawDescGZIP(), []int{86} } func (x *CommitInfo) GetSha() string { @@ -4875,7 +5259,7 @@ type GetResourceHistoryRequest struct { func (x *GetResourceHistoryRequest) Reset() { *x = GetResourceHistoryRequest{} - mi := &file_api_proto_msgTypes[80] + mi := &file_api_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4887,7 +5271,7 @@ func (x *GetResourceHistoryRequest) String() string { func (*GetResourceHistoryRequest) ProtoMessage() {} func (x *GetResourceHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[80] + mi := &file_api_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4900,7 +5284,7 @@ func (x *GetResourceHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetResourceHistoryRequest.ProtoReflect.Descriptor instead. func (*GetResourceHistoryRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{80} + return file_api_proto_rawDescGZIP(), []int{87} } func (x *GetResourceHistoryRequest) GetApiVersion() string { @@ -4933,7 +5317,7 @@ type GetResourceHistoryResponse struct { func (x *GetResourceHistoryResponse) Reset() { *x = GetResourceHistoryResponse{} - mi := &file_api_proto_msgTypes[81] + mi := &file_api_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4945,7 +5329,7 @@ func (x *GetResourceHistoryResponse) String() string { func (*GetResourceHistoryResponse) ProtoMessage() {} func (x *GetResourceHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[81] + mi := &file_api_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4958,7 +5342,7 @@ func (x *GetResourceHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetResourceHistoryResponse.ProtoReflect.Descriptor instead. func (*GetResourceHistoryResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{81} + return file_api_proto_rawDescGZIP(), []int{88} } func (x *GetResourceHistoryResponse) GetCommits() []*CommitInfo { @@ -4980,7 +5364,7 @@ type GetResourceAtCommitRequest struct { func (x *GetResourceAtCommitRequest) Reset() { *x = GetResourceAtCommitRequest{} - mi := &file_api_proto_msgTypes[82] + mi := &file_api_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4992,7 +5376,7 @@ func (x *GetResourceAtCommitRequest) String() string { func (*GetResourceAtCommitRequest) ProtoMessage() {} func (x *GetResourceAtCommitRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[82] + mi := &file_api_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5005,7 +5389,7 @@ func (x *GetResourceAtCommitRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetResourceAtCommitRequest.ProtoReflect.Descriptor instead. func (*GetResourceAtCommitRequest) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{82} + return file_api_proto_rawDescGZIP(), []int{89} } func (x *GetResourceAtCommitRequest) GetApiVersion() string { @@ -5046,7 +5430,7 @@ type GetResourceAtCommitResponse struct { func (x *GetResourceAtCommitResponse) Reset() { *x = GetResourceAtCommitResponse{} - mi := &file_api_proto_msgTypes[83] + mi := &file_api_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5058,7 +5442,7 @@ func (x *GetResourceAtCommitResponse) String() string { func (*GetResourceAtCommitResponse) ProtoMessage() {} func (x *GetResourceAtCommitResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_proto_msgTypes[83] + mi := &file_api_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5071,7 +5455,7 @@ func (x *GetResourceAtCommitResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetResourceAtCommitResponse.ProtoReflect.Descriptor instead. func (*GetResourceAtCommitResponse) Descriptor() ([]byte, []int) { - return file_api_proto_rawDescGZIP(), []int{83} + return file_api_proto_rawDescGZIP(), []int{90} } func (x *GetResourceAtCommitResponse) GetYaml() string { @@ -5102,7 +5486,31 @@ const file_api_proto_rawDesc = "" + "\n" + "git_commit\x18\x03 \x01(\tR\tgitCommit\x12\x1d\n" + "\n" + - "build_time\x18\x04 \x01(\tR\tbuildTime\"I\n" + + "build_time\x18\x04 \x01(\tR\tbuildTime\"\xc7\x01\n" + + "\x0fMigrationBundle\x12\x18\n" + + "\aversion\x18\x01 \x01(\rR\aversion\x120\n" + + "\bresource\x18\x02 \x01(\v2\x14.openctl.v1.ResourceR\bresource\x12#\n" + + "\rcontroller_id\x18\x03 \x01(\tR\fcontrollerId\x12'\n" + + "\x0fmanifest_digest\x18\x04 \x01(\tR\x0emanifestDigest\x12\x1a\n" + + "\bidentity\x18\x05 \x01(\tR\bidentity\"`\n" + + "\x15ExportResourceRequest\x12\x1f\n" + + "\vapi_version\x18\x01 \x01(\tR\n" + + "apiVersion\x12\x12\n" + + "\x04kind\x18\x02 \x01(\tR\x04kind\x12\x12\n" + + "\x04name\x18\x03 \x01(\tR\x04name\"M\n" + + "\x16ExportResourceResponse\x123\n" + + "\x06bundle\x18\x01 \x01(\v2\x1b.openctl.v1.MigrationBundleR\x06bundle\"\xa8\x01\n" + + "\x14AdoptResourceRequest\x120\n" + + "\bresource\x18\x01 \x01(\v2\x14.openctl.v1.ResourceR\bresource\x123\n" + + "\x06bundle\x18\x02 \x01(\v2\x1b.openctl.v1.MigrationBundleR\x06bundle\x12)\n" + + "\x10provider_context\x18\x03 \x01(\tR\x0fproviderContext\"c\n" + + "\x15AdoptResourceResponse\x120\n" + + "\bresource\x18\x01 \x01(\v2\x14.openctl.v1.ResourceR\bresource\x12\x18\n" + + "\amessage\x18\x02 \x01(\tR\amessage\"L\n" + + "\x15ForgetResourceRequest\x123\n" + + "\x06bundle\x18\x01 \x01(\v2\x1b.openctl.v1.MigrationBundleR\x06bundle\"2\n" + + "\x16ForgetResourceResponse\x12\x18\n" + + "\amessage\x18\x01 \x01(\tR\amessage\"I\n" + "\x12ListActionsRequest\x12\x1f\n" + "\vapi_version\x18\x01 \x01(\tR\n" + "apiVersion\x12\x12\n" + @@ -5466,7 +5874,8 @@ const file_api_proto_rawDesc = "" + "\aexisted\x18\x02 \x01(\bR\aexisted2Z\n" + "\vPingService\x12K\n" + "\x04Ping\x12\x17.openctl.v1.PingRequest\x1a\x18.openctl.v1.PingResponse\"\x10\x82\xd3\xe4\x93\x02\n" + - "\x12\b/v1/ping2\xcc\a\n" + + "\x12\b/v1/ping2\xb6\n" + + "\n" + "\x0fResourceService\x12\\\n" + "\x05Apply\x12\x18.openctl.v1.ApplyRequest\x1a\x19.openctl.v1.ApplyResponse\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/v1/resources:apply\x12T\n" + "\x03Get\x12\x16.openctl.v1.GetRequest\x1a\x17.openctl.v1.GetResponse\"\x1c\x82\xd3\xe4\x93\x02\x16:\x01*\"\x11/v1/resources:get\x12X\n" + @@ -5476,7 +5885,10 @@ const file_api_proto_rawDesc = "" + "\vDryRunApply\x12\x1e.openctl.v1.DryRunApplyRequest\x1a\x1f.openctl.v1.DryRunApplyResponse\"$\x82\xd3\xe4\x93\x02\x1e:\x01*\"\x19/v1/resources:dryRunApply\x12t\n" + "\vListActions\x12\x1e.openctl.v1.ListActionsRequest\x1a\x1f.openctl.v1.ListActionsResponse\"$\x82\xd3\xe4\x93\x02\x1e:\x01*\"\x19/v1/resources:listActions\x12x\n" + "\fInvokeAction\x12\x1f.openctl.v1.InvokeActionRequest\x1a .openctl.v1.InvokeActionResponse\"%\x82\xd3\xe4\x93\x02\x1f:\x01*\"\x1a/v1/resources:invokeAction\x12\x85\x01\n" + - "\x10GetChildrenGraph\x12#.openctl.v1.GetChildrenGraphRequest\x1a$.openctl.v1.GetChildrenGraphResponse\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/v1/resources:childrenGraph2\x97\x05\n" + + "\x10GetChildrenGraph\x12#.openctl.v1.GetChildrenGraphRequest\x1a$.openctl.v1.GetChildrenGraphResponse\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/v1/resources:childrenGraph\x12x\n" + + "\x0eExportResource\x12!.openctl.v1.ExportResourceRequest\x1a\".openctl.v1.ExportResourceResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/v1/resources:export\x12t\n" + + "\rAdoptResource\x12 .openctl.v1.AdoptResourceRequest\x1a!.openctl.v1.AdoptResourceResponse\"\x1e\x82\xd3\xe4\x93\x02\x18:\x01*\"\x13/v1/resources:adopt\x12x\n" + + "\x0eForgetResource\x12!.openctl.v1.ForgetResourceRequest\x1a\".openctl.v1.ForgetResourceResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/v1/resources:forget2\x97\x05\n" + "\rConfigService\x12r\n" + "\rListProviders\x12 .openctl.v1.ListProvidersRequest\x1a!.openctl.v1.ListProvidersResponse\"\x1c\x82\xd3\xe4\x93\x02\x16\x12\x14/v1/config/providers\x12x\n" + "\x0eUpsertProvider\x12!.openctl.v1.UpsertProviderRequest\x1a\".openctl.v1.UpsertProviderResponse\"\x1f\x82\xd3\xe4\x93\x02\x19:\x01*\"\x14/v1/config/providers\x12|\n" + @@ -5521,213 +5933,232 @@ func file_api_proto_rawDescGZIP() []byte { } var file_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 87) +var file_api_proto_msgTypes = make([]protoimpl.MessageInfo, 94) var file_api_proto_goTypes = []any{ (WatchEvent_Type)(0), // 0: openctl.v1.WatchEvent.Type (*PingRequest)(nil), // 1: openctl.v1.PingRequest (*PingResponse)(nil), // 2: openctl.v1.PingResponse - (*ListActionsRequest)(nil), // 3: openctl.v1.ListActionsRequest - (*ListActionsResponse)(nil), // 4: openctl.v1.ListActionsResponse - (*ActionSpec)(nil), // 5: openctl.v1.ActionSpec - (*ActionParameterSpec)(nil), // 6: openctl.v1.ActionParameterSpec - (*InvokeActionRequest)(nil), // 7: openctl.v1.InvokeActionRequest - (*InvokeActionResponse)(nil), // 8: openctl.v1.InvokeActionResponse - (*GetChildrenGraphRequest)(nil), // 9: openctl.v1.GetChildrenGraphRequest - (*GraphNode)(nil), // 10: openctl.v1.GraphNode - (*GraphEdge)(nil), // 11: openctl.v1.GraphEdge - (*GetChildrenGraphResponse)(nil), // 12: openctl.v1.GetChildrenGraphResponse - (*DryRunApplyRequest)(nil), // 13: openctl.v1.DryRunApplyRequest - (*DryRunApplyResponse)(nil), // 14: openctl.v1.DryRunApplyResponse - (*FieldError)(nil), // 15: openctl.v1.FieldError - (*ChildAction)(nil), // 16: openctl.v1.ChildAction - (*Resource)(nil), // 17: openctl.v1.Resource - (*ResourceRef)(nil), // 18: openctl.v1.ResourceRef - (*DriftEntry)(nil), // 19: openctl.v1.DriftEntry - (*Metadata)(nil), // 20: openctl.v1.Metadata - (*ApplyRequest)(nil), // 21: openctl.v1.ApplyRequest - (*ApplyResponse)(nil), // 22: openctl.v1.ApplyResponse - (*GetRequest)(nil), // 23: openctl.v1.GetRequest - (*GetResponse)(nil), // 24: openctl.v1.GetResponse - (*ListRequest)(nil), // 25: openctl.v1.ListRequest - (*ListResponse)(nil), // 26: openctl.v1.ListResponse - (*DeleteRequest)(nil), // 27: openctl.v1.DeleteRequest - (*DeleteResponse)(nil), // 28: openctl.v1.DeleteResponse - (*WatchRequest)(nil), // 29: openctl.v1.WatchRequest - (*WatchEvent)(nil), // 30: openctl.v1.WatchEvent - (*ListProvidersRequest)(nil), // 31: openctl.v1.ListProvidersRequest - (*ListProvidersResponse)(nil), // 32: openctl.v1.ListProvidersResponse - (*ProviderEntry)(nil), // 33: openctl.v1.ProviderEntry - (*UpsertProviderRequest)(nil), // 34: openctl.v1.UpsertProviderRequest - (*UpsertProviderResponse)(nil), // 35: openctl.v1.UpsertProviderResponse - (*DeleteProviderRequest)(nil), // 36: openctl.v1.DeleteProviderRequest - (*DeleteProviderResponse)(nil), // 37: openctl.v1.DeleteProviderResponse - (*ControllerConfig)(nil), // 38: openctl.v1.ControllerConfig - (*GetControllerConfigRequest)(nil), // 39: openctl.v1.GetControllerConfigRequest - (*GetControllerConfigResponse)(nil), // 40: openctl.v1.GetControllerConfigResponse - (*UpdateControllerConfigRequest)(nil), // 41: openctl.v1.UpdateControllerConfigRequest - (*UpdateControllerConfigResponse)(nil), // 42: openctl.v1.UpdateControllerConfigResponse - (*ListTemplatesRequest)(nil), // 43: openctl.v1.ListTemplatesRequest - (*ListTemplatesResponse)(nil), // 44: openctl.v1.ListTemplatesResponse - (*TemplateSummary)(nil), // 45: openctl.v1.TemplateSummary - (*GetTemplateRequest)(nil), // 46: openctl.v1.GetTemplateRequest - (*GetTemplateResponse)(nil), // 47: openctl.v1.GetTemplateResponse - (*TemplateParameter)(nil), // 48: openctl.v1.TemplateParameter - (*RenderTemplateRequest)(nil), // 49: openctl.v1.RenderTemplateRequest - (*RenderTemplateResponse)(nil), // 50: openctl.v1.RenderTemplateResponse - (*Operation)(nil), // 51: openctl.v1.Operation - (*GetOperationRequest)(nil), // 52: openctl.v1.GetOperationRequest - (*ListOperationsRequest)(nil), // 53: openctl.v1.ListOperationsRequest - (*ListOperationsResponse)(nil), // 54: openctl.v1.ListOperationsResponse - (*CancelOperationRequest)(nil), // 55: openctl.v1.CancelOperationRequest - (*CancelOperationResponse)(nil), // 56: openctl.v1.CancelOperationResponse - (*LoginRequest)(nil), // 57: openctl.v1.LoginRequest - (*LoginResponse)(nil), // 58: openctl.v1.LoginResponse - (*LogoutRequest)(nil), // 59: openctl.v1.LogoutRequest - (*LogoutResponse)(nil), // 60: openctl.v1.LogoutResponse - (*WhoAmIRequest)(nil), // 61: openctl.v1.WhoAmIRequest - (*WhoAmIResponse)(nil), // 62: openctl.v1.WhoAmIResponse - (*ListSchemasRequest)(nil), // 63: openctl.v1.ListSchemasRequest - (*ListSchemasResponse)(nil), // 64: openctl.v1.ListSchemasResponse - (*SchemaInfo)(nil), // 65: openctl.v1.SchemaInfo - (*GetSchemaRequest)(nil), // 66: openctl.v1.GetSchemaRequest - (*GetSchemaResponse)(nil), // 67: openctl.v1.GetSchemaResponse - (*ValidateRequest)(nil), // 68: openctl.v1.ValidateRequest - (*ValidateResponse)(nil), // 69: openctl.v1.ValidateResponse - (*GetFormSchemaRequest)(nil), // 70: openctl.v1.GetFormSchemaRequest - (*GetFormSchemaResponse)(nil), // 71: openctl.v1.GetFormSchemaResponse - (*WatchOperationsRequest)(nil), // 72: openctl.v1.WatchOperationsRequest - (*OperationEvent)(nil), // 73: openctl.v1.OperationEvent - (*GetRepoStatusRequest)(nil), // 74: openctl.v1.GetRepoStatusRequest - (*GetRepoStatusResponse)(nil), // 75: openctl.v1.GetRepoStatusResponse - (*PushRepoRequest)(nil), // 76: openctl.v1.PushRepoRequest - (*PushRepoResponse)(nil), // 77: openctl.v1.PushRepoResponse - (*PullRepoRequest)(nil), // 78: openctl.v1.PullRepoRequest - (*PullRepoResponse)(nil), // 79: openctl.v1.PullRepoResponse - (*CommitInfo)(nil), // 80: openctl.v1.CommitInfo - (*GetResourceHistoryRequest)(nil), // 81: openctl.v1.GetResourceHistoryRequest - (*GetResourceHistoryResponse)(nil), // 82: openctl.v1.GetResourceHistoryResponse - (*GetResourceAtCommitRequest)(nil), // 83: openctl.v1.GetResourceAtCommitRequest - (*GetResourceAtCommitResponse)(nil), // 84: openctl.v1.GetResourceAtCommitResponse - nil, // 85: openctl.v1.InvokeActionRequest.ParametersEntry - nil, // 86: openctl.v1.Metadata.LabelsEntry - nil, // 87: openctl.v1.Metadata.AnnotationsEntry - (*structpb.Struct)(nil), // 88: google.protobuf.Struct + (*MigrationBundle)(nil), // 3: openctl.v1.MigrationBundle + (*ExportResourceRequest)(nil), // 4: openctl.v1.ExportResourceRequest + (*ExportResourceResponse)(nil), // 5: openctl.v1.ExportResourceResponse + (*AdoptResourceRequest)(nil), // 6: openctl.v1.AdoptResourceRequest + (*AdoptResourceResponse)(nil), // 7: openctl.v1.AdoptResourceResponse + (*ForgetResourceRequest)(nil), // 8: openctl.v1.ForgetResourceRequest + (*ForgetResourceResponse)(nil), // 9: openctl.v1.ForgetResourceResponse + (*ListActionsRequest)(nil), // 10: openctl.v1.ListActionsRequest + (*ListActionsResponse)(nil), // 11: openctl.v1.ListActionsResponse + (*ActionSpec)(nil), // 12: openctl.v1.ActionSpec + (*ActionParameterSpec)(nil), // 13: openctl.v1.ActionParameterSpec + (*InvokeActionRequest)(nil), // 14: openctl.v1.InvokeActionRequest + (*InvokeActionResponse)(nil), // 15: openctl.v1.InvokeActionResponse + (*GetChildrenGraphRequest)(nil), // 16: openctl.v1.GetChildrenGraphRequest + (*GraphNode)(nil), // 17: openctl.v1.GraphNode + (*GraphEdge)(nil), // 18: openctl.v1.GraphEdge + (*GetChildrenGraphResponse)(nil), // 19: openctl.v1.GetChildrenGraphResponse + (*DryRunApplyRequest)(nil), // 20: openctl.v1.DryRunApplyRequest + (*DryRunApplyResponse)(nil), // 21: openctl.v1.DryRunApplyResponse + (*FieldError)(nil), // 22: openctl.v1.FieldError + (*ChildAction)(nil), // 23: openctl.v1.ChildAction + (*Resource)(nil), // 24: openctl.v1.Resource + (*ResourceRef)(nil), // 25: openctl.v1.ResourceRef + (*DriftEntry)(nil), // 26: openctl.v1.DriftEntry + (*Metadata)(nil), // 27: openctl.v1.Metadata + (*ApplyRequest)(nil), // 28: openctl.v1.ApplyRequest + (*ApplyResponse)(nil), // 29: openctl.v1.ApplyResponse + (*GetRequest)(nil), // 30: openctl.v1.GetRequest + (*GetResponse)(nil), // 31: openctl.v1.GetResponse + (*ListRequest)(nil), // 32: openctl.v1.ListRequest + (*ListResponse)(nil), // 33: openctl.v1.ListResponse + (*DeleteRequest)(nil), // 34: openctl.v1.DeleteRequest + (*DeleteResponse)(nil), // 35: openctl.v1.DeleteResponse + (*WatchRequest)(nil), // 36: openctl.v1.WatchRequest + (*WatchEvent)(nil), // 37: openctl.v1.WatchEvent + (*ListProvidersRequest)(nil), // 38: openctl.v1.ListProvidersRequest + (*ListProvidersResponse)(nil), // 39: openctl.v1.ListProvidersResponse + (*ProviderEntry)(nil), // 40: openctl.v1.ProviderEntry + (*UpsertProviderRequest)(nil), // 41: openctl.v1.UpsertProviderRequest + (*UpsertProviderResponse)(nil), // 42: openctl.v1.UpsertProviderResponse + (*DeleteProviderRequest)(nil), // 43: openctl.v1.DeleteProviderRequest + (*DeleteProviderResponse)(nil), // 44: openctl.v1.DeleteProviderResponse + (*ControllerConfig)(nil), // 45: openctl.v1.ControllerConfig + (*GetControllerConfigRequest)(nil), // 46: openctl.v1.GetControllerConfigRequest + (*GetControllerConfigResponse)(nil), // 47: openctl.v1.GetControllerConfigResponse + (*UpdateControllerConfigRequest)(nil), // 48: openctl.v1.UpdateControllerConfigRequest + (*UpdateControllerConfigResponse)(nil), // 49: openctl.v1.UpdateControllerConfigResponse + (*ListTemplatesRequest)(nil), // 50: openctl.v1.ListTemplatesRequest + (*ListTemplatesResponse)(nil), // 51: openctl.v1.ListTemplatesResponse + (*TemplateSummary)(nil), // 52: openctl.v1.TemplateSummary + (*GetTemplateRequest)(nil), // 53: openctl.v1.GetTemplateRequest + (*GetTemplateResponse)(nil), // 54: openctl.v1.GetTemplateResponse + (*TemplateParameter)(nil), // 55: openctl.v1.TemplateParameter + (*RenderTemplateRequest)(nil), // 56: openctl.v1.RenderTemplateRequest + (*RenderTemplateResponse)(nil), // 57: openctl.v1.RenderTemplateResponse + (*Operation)(nil), // 58: openctl.v1.Operation + (*GetOperationRequest)(nil), // 59: openctl.v1.GetOperationRequest + (*ListOperationsRequest)(nil), // 60: openctl.v1.ListOperationsRequest + (*ListOperationsResponse)(nil), // 61: openctl.v1.ListOperationsResponse + (*CancelOperationRequest)(nil), // 62: openctl.v1.CancelOperationRequest + (*CancelOperationResponse)(nil), // 63: openctl.v1.CancelOperationResponse + (*LoginRequest)(nil), // 64: openctl.v1.LoginRequest + (*LoginResponse)(nil), // 65: openctl.v1.LoginResponse + (*LogoutRequest)(nil), // 66: openctl.v1.LogoutRequest + (*LogoutResponse)(nil), // 67: openctl.v1.LogoutResponse + (*WhoAmIRequest)(nil), // 68: openctl.v1.WhoAmIRequest + (*WhoAmIResponse)(nil), // 69: openctl.v1.WhoAmIResponse + (*ListSchemasRequest)(nil), // 70: openctl.v1.ListSchemasRequest + (*ListSchemasResponse)(nil), // 71: openctl.v1.ListSchemasResponse + (*SchemaInfo)(nil), // 72: openctl.v1.SchemaInfo + (*GetSchemaRequest)(nil), // 73: openctl.v1.GetSchemaRequest + (*GetSchemaResponse)(nil), // 74: openctl.v1.GetSchemaResponse + (*ValidateRequest)(nil), // 75: openctl.v1.ValidateRequest + (*ValidateResponse)(nil), // 76: openctl.v1.ValidateResponse + (*GetFormSchemaRequest)(nil), // 77: openctl.v1.GetFormSchemaRequest + (*GetFormSchemaResponse)(nil), // 78: openctl.v1.GetFormSchemaResponse + (*WatchOperationsRequest)(nil), // 79: openctl.v1.WatchOperationsRequest + (*OperationEvent)(nil), // 80: openctl.v1.OperationEvent + (*GetRepoStatusRequest)(nil), // 81: openctl.v1.GetRepoStatusRequest + (*GetRepoStatusResponse)(nil), // 82: openctl.v1.GetRepoStatusResponse + (*PushRepoRequest)(nil), // 83: openctl.v1.PushRepoRequest + (*PushRepoResponse)(nil), // 84: openctl.v1.PushRepoResponse + (*PullRepoRequest)(nil), // 85: openctl.v1.PullRepoRequest + (*PullRepoResponse)(nil), // 86: openctl.v1.PullRepoResponse + (*CommitInfo)(nil), // 87: openctl.v1.CommitInfo + (*GetResourceHistoryRequest)(nil), // 88: openctl.v1.GetResourceHistoryRequest + (*GetResourceHistoryResponse)(nil), // 89: openctl.v1.GetResourceHistoryResponse + (*GetResourceAtCommitRequest)(nil), // 90: openctl.v1.GetResourceAtCommitRequest + (*GetResourceAtCommitResponse)(nil), // 91: openctl.v1.GetResourceAtCommitResponse + nil, // 92: openctl.v1.InvokeActionRequest.ParametersEntry + nil, // 93: openctl.v1.Metadata.LabelsEntry + nil, // 94: openctl.v1.Metadata.AnnotationsEntry + (*structpb.Struct)(nil), // 95: google.protobuf.Struct } var file_api_proto_depIdxs = []int32{ - 5, // 0: openctl.v1.ListActionsResponse.action_specs:type_name -> openctl.v1.ActionSpec - 6, // 1: openctl.v1.ActionSpec.parameters:type_name -> openctl.v1.ActionParameterSpec - 85, // 2: openctl.v1.InvokeActionRequest.parameters:type_name -> openctl.v1.InvokeActionRequest.ParametersEntry - 10, // 3: openctl.v1.GetChildrenGraphResponse.nodes:type_name -> openctl.v1.GraphNode - 11, // 4: openctl.v1.GetChildrenGraphResponse.edges:type_name -> openctl.v1.GraphEdge - 17, // 5: openctl.v1.DryRunApplyRequest.resource:type_name -> openctl.v1.Resource - 19, // 6: openctl.v1.DryRunApplyResponse.diff:type_name -> openctl.v1.DriftEntry - 16, // 7: openctl.v1.DryRunApplyResponse.children:type_name -> openctl.v1.ChildAction - 15, // 8: openctl.v1.DryRunApplyResponse.field_errors:type_name -> openctl.v1.FieldError - 20, // 9: openctl.v1.Resource.metadata:type_name -> openctl.v1.Metadata - 88, // 10: openctl.v1.Resource.spec:type_name -> google.protobuf.Struct - 88, // 11: openctl.v1.Resource.status:type_name -> google.protobuf.Struct - 19, // 12: openctl.v1.Resource.drift:type_name -> openctl.v1.DriftEntry - 18, // 13: openctl.v1.Resource.children:type_name -> openctl.v1.ResourceRef - 86, // 14: openctl.v1.Metadata.labels:type_name -> openctl.v1.Metadata.LabelsEntry - 87, // 15: openctl.v1.Metadata.annotations:type_name -> openctl.v1.Metadata.AnnotationsEntry - 18, // 16: openctl.v1.Metadata.owner_refs:type_name -> openctl.v1.ResourceRef - 17, // 17: openctl.v1.ApplyRequest.resource:type_name -> openctl.v1.Resource - 17, // 18: openctl.v1.GetResponse.resource:type_name -> openctl.v1.Resource - 17, // 19: openctl.v1.GetResponse.applied:type_name -> openctl.v1.Resource - 17, // 20: openctl.v1.ListResponse.resources:type_name -> openctl.v1.Resource - 0, // 21: openctl.v1.WatchEvent.type:type_name -> openctl.v1.WatchEvent.Type - 17, // 22: openctl.v1.WatchEvent.resource:type_name -> openctl.v1.Resource - 33, // 23: openctl.v1.ListProvidersResponse.providers:type_name -> openctl.v1.ProviderEntry - 33, // 24: openctl.v1.UpsertProviderResponse.provider:type_name -> openctl.v1.ProviderEntry - 38, // 25: openctl.v1.GetControllerConfigResponse.config:type_name -> openctl.v1.ControllerConfig - 38, // 26: openctl.v1.UpdateControllerConfigRequest.config:type_name -> openctl.v1.ControllerConfig - 38, // 27: openctl.v1.UpdateControllerConfigResponse.config:type_name -> openctl.v1.ControllerConfig - 45, // 28: openctl.v1.ListTemplatesResponse.templates:type_name -> openctl.v1.TemplateSummary - 45, // 29: openctl.v1.GetTemplateResponse.summary:type_name -> openctl.v1.TemplateSummary - 48, // 30: openctl.v1.GetTemplateResponse.parameters:type_name -> openctl.v1.TemplateParameter - 88, // 31: openctl.v1.RenderTemplateRequest.params:type_name -> google.protobuf.Struct - 17, // 32: openctl.v1.RenderTemplateResponse.resource:type_name -> openctl.v1.Resource - 17, // 33: openctl.v1.Operation.result:type_name -> openctl.v1.Resource - 51, // 34: openctl.v1.Operation.children:type_name -> openctl.v1.Operation - 51, // 35: openctl.v1.ListOperationsResponse.operations:type_name -> openctl.v1.Operation - 65, // 36: openctl.v1.ListSchemasResponse.schemas:type_name -> openctl.v1.SchemaInfo - 65, // 37: openctl.v1.GetSchemaResponse.info:type_name -> openctl.v1.SchemaInfo - 17, // 38: openctl.v1.ValidateRequest.resource:type_name -> openctl.v1.Resource - 51, // 39: openctl.v1.OperationEvent.operation:type_name -> openctl.v1.Operation - 80, // 40: openctl.v1.GetResourceHistoryResponse.commits:type_name -> openctl.v1.CommitInfo - 1, // 41: openctl.v1.PingService.Ping:input_type -> openctl.v1.PingRequest - 21, // 42: openctl.v1.ResourceService.Apply:input_type -> openctl.v1.ApplyRequest - 23, // 43: openctl.v1.ResourceService.Get:input_type -> openctl.v1.GetRequest - 25, // 44: openctl.v1.ResourceService.List:input_type -> openctl.v1.ListRequest - 27, // 45: openctl.v1.ResourceService.Delete:input_type -> openctl.v1.DeleteRequest - 29, // 46: openctl.v1.ResourceService.Watch:input_type -> openctl.v1.WatchRequest - 13, // 47: openctl.v1.ResourceService.DryRunApply:input_type -> openctl.v1.DryRunApplyRequest - 3, // 48: openctl.v1.ResourceService.ListActions:input_type -> openctl.v1.ListActionsRequest - 7, // 49: openctl.v1.ResourceService.InvokeAction:input_type -> openctl.v1.InvokeActionRequest - 9, // 50: openctl.v1.ResourceService.GetChildrenGraph:input_type -> openctl.v1.GetChildrenGraphRequest - 31, // 51: openctl.v1.ConfigService.ListProviders:input_type -> openctl.v1.ListProvidersRequest - 34, // 52: openctl.v1.ConfigService.UpsertProvider:input_type -> openctl.v1.UpsertProviderRequest - 36, // 53: openctl.v1.ConfigService.DeleteProvider:input_type -> openctl.v1.DeleteProviderRequest - 39, // 54: openctl.v1.ConfigService.GetControllerConfig:input_type -> openctl.v1.GetControllerConfigRequest - 41, // 55: openctl.v1.ConfigService.UpdateControllerConfig:input_type -> openctl.v1.UpdateControllerConfigRequest - 43, // 56: openctl.v1.TemplateService.ListTemplates:input_type -> openctl.v1.ListTemplatesRequest - 46, // 57: openctl.v1.TemplateService.GetTemplate:input_type -> openctl.v1.GetTemplateRequest - 49, // 58: openctl.v1.TemplateService.RenderTemplate:input_type -> openctl.v1.RenderTemplateRequest - 52, // 59: openctl.v1.OperationService.GetOperation:input_type -> openctl.v1.GetOperationRequest - 53, // 60: openctl.v1.OperationService.ListOperations:input_type -> openctl.v1.ListOperationsRequest - 72, // 61: openctl.v1.OperationService.WatchOperations:input_type -> openctl.v1.WatchOperationsRequest - 55, // 62: openctl.v1.OperationService.CancelOperation:input_type -> openctl.v1.CancelOperationRequest - 57, // 63: openctl.v1.SessionService.Login:input_type -> openctl.v1.LoginRequest - 59, // 64: openctl.v1.SessionService.Logout:input_type -> openctl.v1.LogoutRequest - 61, // 65: openctl.v1.SessionService.WhoAmI:input_type -> openctl.v1.WhoAmIRequest - 63, // 66: openctl.v1.SchemaService.ListSchemas:input_type -> openctl.v1.ListSchemasRequest - 66, // 67: openctl.v1.SchemaService.GetSchema:input_type -> openctl.v1.GetSchemaRequest - 68, // 68: openctl.v1.SchemaService.Validate:input_type -> openctl.v1.ValidateRequest - 70, // 69: openctl.v1.SchemaService.GetFormSchema:input_type -> openctl.v1.GetFormSchemaRequest - 74, // 70: openctl.v1.RepoService.GetStatus:input_type -> openctl.v1.GetRepoStatusRequest - 76, // 71: openctl.v1.RepoService.Push:input_type -> openctl.v1.PushRepoRequest - 78, // 72: openctl.v1.RepoService.Pull:input_type -> openctl.v1.PullRepoRequest - 81, // 73: openctl.v1.RepoService.GetResourceHistory:input_type -> openctl.v1.GetResourceHistoryRequest - 83, // 74: openctl.v1.RepoService.GetResourceAtCommit:input_type -> openctl.v1.GetResourceAtCommitRequest - 2, // 75: openctl.v1.PingService.Ping:output_type -> openctl.v1.PingResponse - 22, // 76: openctl.v1.ResourceService.Apply:output_type -> openctl.v1.ApplyResponse - 24, // 77: openctl.v1.ResourceService.Get:output_type -> openctl.v1.GetResponse - 26, // 78: openctl.v1.ResourceService.List:output_type -> openctl.v1.ListResponse - 28, // 79: openctl.v1.ResourceService.Delete:output_type -> openctl.v1.DeleteResponse - 30, // 80: openctl.v1.ResourceService.Watch:output_type -> openctl.v1.WatchEvent - 14, // 81: openctl.v1.ResourceService.DryRunApply:output_type -> openctl.v1.DryRunApplyResponse - 4, // 82: openctl.v1.ResourceService.ListActions:output_type -> openctl.v1.ListActionsResponse - 8, // 83: openctl.v1.ResourceService.InvokeAction:output_type -> openctl.v1.InvokeActionResponse - 12, // 84: openctl.v1.ResourceService.GetChildrenGraph:output_type -> openctl.v1.GetChildrenGraphResponse - 32, // 85: openctl.v1.ConfigService.ListProviders:output_type -> openctl.v1.ListProvidersResponse - 35, // 86: openctl.v1.ConfigService.UpsertProvider:output_type -> openctl.v1.UpsertProviderResponse - 37, // 87: openctl.v1.ConfigService.DeleteProvider:output_type -> openctl.v1.DeleteProviderResponse - 40, // 88: openctl.v1.ConfigService.GetControllerConfig:output_type -> openctl.v1.GetControllerConfigResponse - 42, // 89: openctl.v1.ConfigService.UpdateControllerConfig:output_type -> openctl.v1.UpdateControllerConfigResponse - 44, // 90: openctl.v1.TemplateService.ListTemplates:output_type -> openctl.v1.ListTemplatesResponse - 47, // 91: openctl.v1.TemplateService.GetTemplate:output_type -> openctl.v1.GetTemplateResponse - 50, // 92: openctl.v1.TemplateService.RenderTemplate:output_type -> openctl.v1.RenderTemplateResponse - 51, // 93: openctl.v1.OperationService.GetOperation:output_type -> openctl.v1.Operation - 54, // 94: openctl.v1.OperationService.ListOperations:output_type -> openctl.v1.ListOperationsResponse - 73, // 95: openctl.v1.OperationService.WatchOperations:output_type -> openctl.v1.OperationEvent - 56, // 96: openctl.v1.OperationService.CancelOperation:output_type -> openctl.v1.CancelOperationResponse - 58, // 97: openctl.v1.SessionService.Login:output_type -> openctl.v1.LoginResponse - 60, // 98: openctl.v1.SessionService.Logout:output_type -> openctl.v1.LogoutResponse - 62, // 99: openctl.v1.SessionService.WhoAmI:output_type -> openctl.v1.WhoAmIResponse - 64, // 100: openctl.v1.SchemaService.ListSchemas:output_type -> openctl.v1.ListSchemasResponse - 67, // 101: openctl.v1.SchemaService.GetSchema:output_type -> openctl.v1.GetSchemaResponse - 69, // 102: openctl.v1.SchemaService.Validate:output_type -> openctl.v1.ValidateResponse - 71, // 103: openctl.v1.SchemaService.GetFormSchema:output_type -> openctl.v1.GetFormSchemaResponse - 75, // 104: openctl.v1.RepoService.GetStatus:output_type -> openctl.v1.GetRepoStatusResponse - 77, // 105: openctl.v1.RepoService.Push:output_type -> openctl.v1.PushRepoResponse - 79, // 106: openctl.v1.RepoService.Pull:output_type -> openctl.v1.PullRepoResponse - 82, // 107: openctl.v1.RepoService.GetResourceHistory:output_type -> openctl.v1.GetResourceHistoryResponse - 84, // 108: openctl.v1.RepoService.GetResourceAtCommit:output_type -> openctl.v1.GetResourceAtCommitResponse - 75, // [75:109] is the sub-list for method output_type - 41, // [41:75] is the sub-list for method input_type - 41, // [41:41] is the sub-list for extension type_name - 41, // [41:41] is the sub-list for extension extendee - 0, // [0:41] is the sub-list for field type_name + 24, // 0: openctl.v1.MigrationBundle.resource:type_name -> openctl.v1.Resource + 3, // 1: openctl.v1.ExportResourceResponse.bundle:type_name -> openctl.v1.MigrationBundle + 24, // 2: openctl.v1.AdoptResourceRequest.resource:type_name -> openctl.v1.Resource + 3, // 3: openctl.v1.AdoptResourceRequest.bundle:type_name -> openctl.v1.MigrationBundle + 24, // 4: openctl.v1.AdoptResourceResponse.resource:type_name -> openctl.v1.Resource + 3, // 5: openctl.v1.ForgetResourceRequest.bundle:type_name -> openctl.v1.MigrationBundle + 12, // 6: openctl.v1.ListActionsResponse.action_specs:type_name -> openctl.v1.ActionSpec + 13, // 7: openctl.v1.ActionSpec.parameters:type_name -> openctl.v1.ActionParameterSpec + 92, // 8: openctl.v1.InvokeActionRequest.parameters:type_name -> openctl.v1.InvokeActionRequest.ParametersEntry + 17, // 9: openctl.v1.GetChildrenGraphResponse.nodes:type_name -> openctl.v1.GraphNode + 18, // 10: openctl.v1.GetChildrenGraphResponse.edges:type_name -> openctl.v1.GraphEdge + 24, // 11: openctl.v1.DryRunApplyRequest.resource:type_name -> openctl.v1.Resource + 26, // 12: openctl.v1.DryRunApplyResponse.diff:type_name -> openctl.v1.DriftEntry + 23, // 13: openctl.v1.DryRunApplyResponse.children:type_name -> openctl.v1.ChildAction + 22, // 14: openctl.v1.DryRunApplyResponse.field_errors:type_name -> openctl.v1.FieldError + 27, // 15: openctl.v1.Resource.metadata:type_name -> openctl.v1.Metadata + 95, // 16: openctl.v1.Resource.spec:type_name -> google.protobuf.Struct + 95, // 17: openctl.v1.Resource.status:type_name -> google.protobuf.Struct + 26, // 18: openctl.v1.Resource.drift:type_name -> openctl.v1.DriftEntry + 25, // 19: openctl.v1.Resource.children:type_name -> openctl.v1.ResourceRef + 93, // 20: openctl.v1.Metadata.labels:type_name -> openctl.v1.Metadata.LabelsEntry + 94, // 21: openctl.v1.Metadata.annotations:type_name -> openctl.v1.Metadata.AnnotationsEntry + 25, // 22: openctl.v1.Metadata.owner_refs:type_name -> openctl.v1.ResourceRef + 24, // 23: openctl.v1.ApplyRequest.resource:type_name -> openctl.v1.Resource + 24, // 24: openctl.v1.GetResponse.resource:type_name -> openctl.v1.Resource + 24, // 25: openctl.v1.GetResponse.applied:type_name -> openctl.v1.Resource + 24, // 26: openctl.v1.ListResponse.resources:type_name -> openctl.v1.Resource + 0, // 27: openctl.v1.WatchEvent.type:type_name -> openctl.v1.WatchEvent.Type + 24, // 28: openctl.v1.WatchEvent.resource:type_name -> openctl.v1.Resource + 40, // 29: openctl.v1.ListProvidersResponse.providers:type_name -> openctl.v1.ProviderEntry + 40, // 30: openctl.v1.UpsertProviderResponse.provider:type_name -> openctl.v1.ProviderEntry + 45, // 31: openctl.v1.GetControllerConfigResponse.config:type_name -> openctl.v1.ControllerConfig + 45, // 32: openctl.v1.UpdateControllerConfigRequest.config:type_name -> openctl.v1.ControllerConfig + 45, // 33: openctl.v1.UpdateControllerConfigResponse.config:type_name -> openctl.v1.ControllerConfig + 52, // 34: openctl.v1.ListTemplatesResponse.templates:type_name -> openctl.v1.TemplateSummary + 52, // 35: openctl.v1.GetTemplateResponse.summary:type_name -> openctl.v1.TemplateSummary + 55, // 36: openctl.v1.GetTemplateResponse.parameters:type_name -> openctl.v1.TemplateParameter + 95, // 37: openctl.v1.RenderTemplateRequest.params:type_name -> google.protobuf.Struct + 24, // 38: openctl.v1.RenderTemplateResponse.resource:type_name -> openctl.v1.Resource + 24, // 39: openctl.v1.Operation.result:type_name -> openctl.v1.Resource + 58, // 40: openctl.v1.Operation.children:type_name -> openctl.v1.Operation + 58, // 41: openctl.v1.ListOperationsResponse.operations:type_name -> openctl.v1.Operation + 72, // 42: openctl.v1.ListSchemasResponse.schemas:type_name -> openctl.v1.SchemaInfo + 72, // 43: openctl.v1.GetSchemaResponse.info:type_name -> openctl.v1.SchemaInfo + 24, // 44: openctl.v1.ValidateRequest.resource:type_name -> openctl.v1.Resource + 58, // 45: openctl.v1.OperationEvent.operation:type_name -> openctl.v1.Operation + 87, // 46: openctl.v1.GetResourceHistoryResponse.commits:type_name -> openctl.v1.CommitInfo + 1, // 47: openctl.v1.PingService.Ping:input_type -> openctl.v1.PingRequest + 28, // 48: openctl.v1.ResourceService.Apply:input_type -> openctl.v1.ApplyRequest + 30, // 49: openctl.v1.ResourceService.Get:input_type -> openctl.v1.GetRequest + 32, // 50: openctl.v1.ResourceService.List:input_type -> openctl.v1.ListRequest + 34, // 51: openctl.v1.ResourceService.Delete:input_type -> openctl.v1.DeleteRequest + 36, // 52: openctl.v1.ResourceService.Watch:input_type -> openctl.v1.WatchRequest + 20, // 53: openctl.v1.ResourceService.DryRunApply:input_type -> openctl.v1.DryRunApplyRequest + 10, // 54: openctl.v1.ResourceService.ListActions:input_type -> openctl.v1.ListActionsRequest + 14, // 55: openctl.v1.ResourceService.InvokeAction:input_type -> openctl.v1.InvokeActionRequest + 16, // 56: openctl.v1.ResourceService.GetChildrenGraph:input_type -> openctl.v1.GetChildrenGraphRequest + 4, // 57: openctl.v1.ResourceService.ExportResource:input_type -> openctl.v1.ExportResourceRequest + 6, // 58: openctl.v1.ResourceService.AdoptResource:input_type -> openctl.v1.AdoptResourceRequest + 8, // 59: openctl.v1.ResourceService.ForgetResource:input_type -> openctl.v1.ForgetResourceRequest + 38, // 60: openctl.v1.ConfigService.ListProviders:input_type -> openctl.v1.ListProvidersRequest + 41, // 61: openctl.v1.ConfigService.UpsertProvider:input_type -> openctl.v1.UpsertProviderRequest + 43, // 62: openctl.v1.ConfigService.DeleteProvider:input_type -> openctl.v1.DeleteProviderRequest + 46, // 63: openctl.v1.ConfigService.GetControllerConfig:input_type -> openctl.v1.GetControllerConfigRequest + 48, // 64: openctl.v1.ConfigService.UpdateControllerConfig:input_type -> openctl.v1.UpdateControllerConfigRequest + 50, // 65: openctl.v1.TemplateService.ListTemplates:input_type -> openctl.v1.ListTemplatesRequest + 53, // 66: openctl.v1.TemplateService.GetTemplate:input_type -> openctl.v1.GetTemplateRequest + 56, // 67: openctl.v1.TemplateService.RenderTemplate:input_type -> openctl.v1.RenderTemplateRequest + 59, // 68: openctl.v1.OperationService.GetOperation:input_type -> openctl.v1.GetOperationRequest + 60, // 69: openctl.v1.OperationService.ListOperations:input_type -> openctl.v1.ListOperationsRequest + 79, // 70: openctl.v1.OperationService.WatchOperations:input_type -> openctl.v1.WatchOperationsRequest + 62, // 71: openctl.v1.OperationService.CancelOperation:input_type -> openctl.v1.CancelOperationRequest + 64, // 72: openctl.v1.SessionService.Login:input_type -> openctl.v1.LoginRequest + 66, // 73: openctl.v1.SessionService.Logout:input_type -> openctl.v1.LogoutRequest + 68, // 74: openctl.v1.SessionService.WhoAmI:input_type -> openctl.v1.WhoAmIRequest + 70, // 75: openctl.v1.SchemaService.ListSchemas:input_type -> openctl.v1.ListSchemasRequest + 73, // 76: openctl.v1.SchemaService.GetSchema:input_type -> openctl.v1.GetSchemaRequest + 75, // 77: openctl.v1.SchemaService.Validate:input_type -> openctl.v1.ValidateRequest + 77, // 78: openctl.v1.SchemaService.GetFormSchema:input_type -> openctl.v1.GetFormSchemaRequest + 81, // 79: openctl.v1.RepoService.GetStatus:input_type -> openctl.v1.GetRepoStatusRequest + 83, // 80: openctl.v1.RepoService.Push:input_type -> openctl.v1.PushRepoRequest + 85, // 81: openctl.v1.RepoService.Pull:input_type -> openctl.v1.PullRepoRequest + 88, // 82: openctl.v1.RepoService.GetResourceHistory:input_type -> openctl.v1.GetResourceHistoryRequest + 90, // 83: openctl.v1.RepoService.GetResourceAtCommit:input_type -> openctl.v1.GetResourceAtCommitRequest + 2, // 84: openctl.v1.PingService.Ping:output_type -> openctl.v1.PingResponse + 29, // 85: openctl.v1.ResourceService.Apply:output_type -> openctl.v1.ApplyResponse + 31, // 86: openctl.v1.ResourceService.Get:output_type -> openctl.v1.GetResponse + 33, // 87: openctl.v1.ResourceService.List:output_type -> openctl.v1.ListResponse + 35, // 88: openctl.v1.ResourceService.Delete:output_type -> openctl.v1.DeleteResponse + 37, // 89: openctl.v1.ResourceService.Watch:output_type -> openctl.v1.WatchEvent + 21, // 90: openctl.v1.ResourceService.DryRunApply:output_type -> openctl.v1.DryRunApplyResponse + 11, // 91: openctl.v1.ResourceService.ListActions:output_type -> openctl.v1.ListActionsResponse + 15, // 92: openctl.v1.ResourceService.InvokeAction:output_type -> openctl.v1.InvokeActionResponse + 19, // 93: openctl.v1.ResourceService.GetChildrenGraph:output_type -> openctl.v1.GetChildrenGraphResponse + 5, // 94: openctl.v1.ResourceService.ExportResource:output_type -> openctl.v1.ExportResourceResponse + 7, // 95: openctl.v1.ResourceService.AdoptResource:output_type -> openctl.v1.AdoptResourceResponse + 9, // 96: openctl.v1.ResourceService.ForgetResource:output_type -> openctl.v1.ForgetResourceResponse + 39, // 97: openctl.v1.ConfigService.ListProviders:output_type -> openctl.v1.ListProvidersResponse + 42, // 98: openctl.v1.ConfigService.UpsertProvider:output_type -> openctl.v1.UpsertProviderResponse + 44, // 99: openctl.v1.ConfigService.DeleteProvider:output_type -> openctl.v1.DeleteProviderResponse + 47, // 100: openctl.v1.ConfigService.GetControllerConfig:output_type -> openctl.v1.GetControllerConfigResponse + 49, // 101: openctl.v1.ConfigService.UpdateControllerConfig:output_type -> openctl.v1.UpdateControllerConfigResponse + 51, // 102: openctl.v1.TemplateService.ListTemplates:output_type -> openctl.v1.ListTemplatesResponse + 54, // 103: openctl.v1.TemplateService.GetTemplate:output_type -> openctl.v1.GetTemplateResponse + 57, // 104: openctl.v1.TemplateService.RenderTemplate:output_type -> openctl.v1.RenderTemplateResponse + 58, // 105: openctl.v1.OperationService.GetOperation:output_type -> openctl.v1.Operation + 61, // 106: openctl.v1.OperationService.ListOperations:output_type -> openctl.v1.ListOperationsResponse + 80, // 107: openctl.v1.OperationService.WatchOperations:output_type -> openctl.v1.OperationEvent + 63, // 108: openctl.v1.OperationService.CancelOperation:output_type -> openctl.v1.CancelOperationResponse + 65, // 109: openctl.v1.SessionService.Login:output_type -> openctl.v1.LoginResponse + 67, // 110: openctl.v1.SessionService.Logout:output_type -> openctl.v1.LogoutResponse + 69, // 111: openctl.v1.SessionService.WhoAmI:output_type -> openctl.v1.WhoAmIResponse + 71, // 112: openctl.v1.SchemaService.ListSchemas:output_type -> openctl.v1.ListSchemasResponse + 74, // 113: openctl.v1.SchemaService.GetSchema:output_type -> openctl.v1.GetSchemaResponse + 76, // 114: openctl.v1.SchemaService.Validate:output_type -> openctl.v1.ValidateResponse + 78, // 115: openctl.v1.SchemaService.GetFormSchema:output_type -> openctl.v1.GetFormSchemaResponse + 82, // 116: openctl.v1.RepoService.GetStatus:output_type -> openctl.v1.GetRepoStatusResponse + 84, // 117: openctl.v1.RepoService.Push:output_type -> openctl.v1.PushRepoResponse + 86, // 118: openctl.v1.RepoService.Pull:output_type -> openctl.v1.PullRepoResponse + 89, // 119: openctl.v1.RepoService.GetResourceHistory:output_type -> openctl.v1.GetResourceHistoryResponse + 91, // 120: openctl.v1.RepoService.GetResourceAtCommit:output_type -> openctl.v1.GetResourceAtCommitResponse + 84, // [84:121] is the sub-list for method output_type + 47, // [47:84] is the sub-list for method input_type + 47, // [47:47] is the sub-list for extension type_name + 47, // [47:47] is the sub-list for extension extendee + 0, // [0:47] is the sub-list for field type_name } func init() { file_api_proto_init() } @@ -5741,7 +6172,7 @@ func file_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_proto_rawDesc), len(file_api_proto_rawDesc)), NumEnums: 1, - NumMessages: 87, + NumMessages: 94, NumExtensions: 0, NumServices: 8, }, diff --git a/pkg/api/v1/api.pb.gw.go b/pkg/api/v1/api.pb.gw.go index 138367d..d2fdf3b 100644 --- a/pkg/api/v1/api.pb.gw.go +++ b/pkg/api/v1/api.pb.gw.go @@ -309,6 +309,87 @@ func local_request_ResourceService_GetChildrenGraph_0(ctx context.Context, marsh return msg, metadata, err } +func request_ResourceService_ExportResource_0(ctx context.Context, marshaler runtime.Marshaler, client ResourceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExportResourceRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.ExportResource(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_ResourceService_ExportResource_0(ctx context.Context, marshaler runtime.Marshaler, server ResourceServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ExportResourceRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ExportResource(ctx, &protoReq) + return msg, metadata, err +} + +func request_ResourceService_AdoptResource_0(ctx context.Context, marshaler runtime.Marshaler, client ResourceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AdoptResourceRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.AdoptResource(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_ResourceService_AdoptResource_0(ctx context.Context, marshaler runtime.Marshaler, server ResourceServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq AdoptResourceRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.AdoptResource(ctx, &protoReq) + return msg, metadata, err +} + +func request_ResourceService_ForgetResource_0(ctx context.Context, marshaler runtime.Marshaler, client ResourceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ForgetResourceRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.ForgetResource(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_ResourceService_ForgetResource_0(ctx context.Context, marshaler runtime.Marshaler, server ResourceServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq ForgetResourceRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.ForgetResource(ctx, &protoReq) + return msg, metadata, err +} + func request_ConfigService_ListProviders_0(ctx context.Context, marshaler runtime.Marshaler, client ConfigServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( protoReq ListProvidersRequest @@ -1206,6 +1287,66 @@ func RegisterResourceServiceHandlerServer(ctx context.Context, mux *runtime.Serv } forward_ResourceService_GetChildrenGraph_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_ResourceService_ExportResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/openctl.v1.ResourceService/ExportResource", runtime.WithHTTPPathPattern("/v1/resources:export")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_ResourceService_ExportResource_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_ResourceService_ExportResource_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_ResourceService_AdoptResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/openctl.v1.ResourceService/AdoptResource", runtime.WithHTTPPathPattern("/v1/resources:adopt")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_ResourceService_AdoptResource_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_ResourceService_AdoptResource_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_ResourceService_ForgetResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/openctl.v1.ResourceService/ForgetResource", runtime.WithHTTPPathPattern("/v1/resources:forget")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_ResourceService_ForgetResource_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_ResourceService_ForgetResource_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -1990,6 +2131,57 @@ func RegisterResourceServiceHandlerClient(ctx context.Context, mux *runtime.Serv } forward_ResourceService_GetChildrenGraph_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_ResourceService_ExportResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/openctl.v1.ResourceService/ExportResource", runtime.WithHTTPPathPattern("/v1/resources:export")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ResourceService_ExportResource_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_ResourceService_ExportResource_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_ResourceService_AdoptResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/openctl.v1.ResourceService/AdoptResource", runtime.WithHTTPPathPattern("/v1/resources:adopt")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ResourceService_AdoptResource_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_ResourceService_AdoptResource_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) + mux.Handle(http.MethodPost, pattern_ResourceService_ForgetResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/openctl.v1.ResourceService/ForgetResource", runtime.WithHTTPPathPattern("/v1/resources:forget")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ResourceService_ForgetResource_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_ResourceService_ForgetResource_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) return nil } @@ -2003,6 +2195,9 @@ var ( pattern_ResourceService_ListActions_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "resources"}, "listActions")) pattern_ResourceService_InvokeAction_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "resources"}, "invokeAction")) pattern_ResourceService_GetChildrenGraph_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "resources"}, "childrenGraph")) + pattern_ResourceService_ExportResource_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "resources"}, "export")) + pattern_ResourceService_AdoptResource_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "resources"}, "adopt")) + pattern_ResourceService_ForgetResource_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "resources"}, "forget")) ) var ( @@ -2015,6 +2210,9 @@ var ( forward_ResourceService_ListActions_0 = runtime.ForwardResponseMessage forward_ResourceService_InvokeAction_0 = runtime.ForwardResponseMessage forward_ResourceService_GetChildrenGraph_0 = runtime.ForwardResponseMessage + forward_ResourceService_ExportResource_0 = runtime.ForwardResponseMessage + forward_ResourceService_AdoptResource_0 = runtime.ForwardResponseMessage + forward_ResourceService_ForgetResource_0 = runtime.ForwardResponseMessage ) // RegisterConfigServiceHandlerFromEndpoint is same as RegisterConfigServiceHandler but diff --git a/pkg/api/v1/api.proto b/pkg/api/v1/api.proto index 57df7fa..7b52db1 100644 --- a/pkg/api/v1/api.proto +++ b/pkg/api/v1/api.proto @@ -36,17 +36,12 @@ message PingResponse { string build_time = 4; } -// ResourceService is the controller's primary surface for managing resources -// declaratively. From Phase 2 it operates synchronously per the -// no-op-on-existing semantics; Phase 3 adds async dispatch via OperationService -// for long-running applies (Cluster create, etc). -// -// Phase 2 supports atomic resources (VirtualMachine). Phase 4 adds composite -// resources (Cluster) which spawn child operations. +// ResourceService manages desired state and observed infrastructure. Apply +// and Delete submit asynchronous operations; migration RPCs synchronously +// transfer controller tracking without applying or deleting infrastructure. service ResourceService { - // Apply creates a resource. Phase 2 is no-op-on-existing: if a resource - // with the manifest's name already exists, the response carries its - // observed state without mutating it. + // Apply converges a resource, creating or updating infrastructure as needed. + // To adopt an existing VM without changing it, use AdoptResource instead. rpc Apply(ApplyRequest) returns (ApplyResponse) { option (google.api.http) = { post: "/v1/resources:apply" body: "*" }; } @@ -116,6 +111,62 @@ service ResourceService { rpc GetChildrenGraph(GetChildrenGraphRequest) returns (GetChildrenGraphResponse) { option (google.api.http) = { post: "/v1/resources:childrenGraph" body: "*" }; } + + // Export preserves ownership and packages desired intent with live identity. + rpc ExportResource(ExportResourceRequest) returns (ExportResourceResponse) { + option (google.api.http) = { post: "/v1/resources:export" body: "*" }; + } + + // Adopt tracks an existing standalone VM without changing the provider. + rpc AdoptResource(AdoptResourceRequest) returns (AdoptResourceResponse) { + option (google.api.http) = { post: "/v1/resources:adopt" body: "*" }; + } + + // Forget releases only controller tracking; the provider VM remains intact. + rpc ForgetResource(ForgetResourceRequest) returns (ForgetResourceResponse) { + option (google.api.http) = { post: "/v1/resources:forget" body: "*" }; + } +} + +// MigrationBundle contains desired intent, not observed state. Treat it as +// sensitive: manifests may contain literal cloud-init credentials. +message MigrationBundle { + uint32 version = 1; + Resource resource = 2; + string controller_id = 3; + string manifest_digest = 4; + string identity = 5; +} + +message ExportResourceRequest { + string api_version = 1; + string kind = 2; + string name = 3; +} + +message ExportResourceResponse { + MigrationBundle bundle = 1; +} + +message AdoptResourceRequest { + // Exactly one of resource and bundle is required. + Resource resource = 1; + MigrationBundle bundle = 2; + // Overrides spec.context at the destination without rewriting the bundle. + string provider_context = 3; +} + +message AdoptResourceResponse { + Resource resource = 1; + string message = 2; +} + +message ForgetResourceRequest { + MigrationBundle bundle = 1; +} + +message ForgetResourceResponse { + string message = 1; } message ListActionsRequest { diff --git a/pkg/api/v1/api_grpc.pb.go b/pkg/api/v1/api_grpc.pb.go index f952548..ebffa00 100644 --- a/pkg/api/v1/api_grpc.pb.go +++ b/pkg/api/v1/api_grpc.pb.go @@ -140,23 +140,21 @@ const ( ResourceService_ListActions_FullMethodName = "/openctl.v1.ResourceService/ListActions" ResourceService_InvokeAction_FullMethodName = "/openctl.v1.ResourceService/InvokeAction" ResourceService_GetChildrenGraph_FullMethodName = "/openctl.v1.ResourceService/GetChildrenGraph" + ResourceService_ExportResource_FullMethodName = "/openctl.v1.ResourceService/ExportResource" + ResourceService_AdoptResource_FullMethodName = "/openctl.v1.ResourceService/AdoptResource" + ResourceService_ForgetResource_FullMethodName = "/openctl.v1.ResourceService/ForgetResource" ) // ResourceServiceClient is the client API for ResourceService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // -// ResourceService is the controller's primary surface for managing resources -// declaratively. From Phase 2 it operates synchronously per the -// no-op-on-existing semantics; Phase 3 adds async dispatch via OperationService -// for long-running applies (Cluster create, etc). -// -// Phase 2 supports atomic resources (VirtualMachine). Phase 4 adds composite -// resources (Cluster) which spawn child operations. +// ResourceService manages desired state and observed infrastructure. Apply +// and Delete submit asynchronous operations; migration RPCs synchronously +// transfer controller tracking without applying or deleting infrastructure. type ResourceServiceClient interface { - // Apply creates a resource. Phase 2 is no-op-on-existing: if a resource - // with the manifest's name already exists, the response carries its - // observed state without mutating it. + // Apply converges a resource, creating or updating infrastructure as needed. + // To adopt an existing VM without changing it, use AdoptResource instead. Apply(ctx context.Context, in *ApplyRequest, opts ...grpc.CallOption) (*ApplyResponse, error) // Get returns the observed state of a single resource. Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*GetResponse, error) @@ -200,6 +198,12 @@ type ResourceServiceClient interface { // applied manifest so the UI can render them dim (Phase U9). Returns an // empty graph (root node only) for atomic resources. GetChildrenGraph(ctx context.Context, in *GetChildrenGraphRequest, opts ...grpc.CallOption) (*GetChildrenGraphResponse, error) + // Export preserves ownership and packages desired intent with live identity. + ExportResource(ctx context.Context, in *ExportResourceRequest, opts ...grpc.CallOption) (*ExportResourceResponse, error) + // Adopt tracks an existing standalone VM without changing the provider. + AdoptResource(ctx context.Context, in *AdoptResourceRequest, opts ...grpc.CallOption) (*AdoptResourceResponse, error) + // Forget releases only controller tracking; the provider VM remains intact. + ForgetResource(ctx context.Context, in *ForgetResourceRequest, opts ...grpc.CallOption) (*ForgetResourceResponse, error) } type resourceServiceClient struct { @@ -309,21 +313,46 @@ func (c *resourceServiceClient) GetChildrenGraph(ctx context.Context, in *GetChi return out, nil } +func (c *resourceServiceClient) ExportResource(ctx context.Context, in *ExportResourceRequest, opts ...grpc.CallOption) (*ExportResourceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ExportResourceResponse) + err := c.cc.Invoke(ctx, ResourceService_ExportResource_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceServiceClient) AdoptResource(ctx context.Context, in *AdoptResourceRequest, opts ...grpc.CallOption) (*AdoptResourceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AdoptResourceResponse) + err := c.cc.Invoke(ctx, ResourceService_AdoptResource_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *resourceServiceClient) ForgetResource(ctx context.Context, in *ForgetResourceRequest, opts ...grpc.CallOption) (*ForgetResourceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ForgetResourceResponse) + err := c.cc.Invoke(ctx, ResourceService_ForgetResource_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // ResourceServiceServer is the server API for ResourceService service. // All implementations must embed UnimplementedResourceServiceServer // for forward compatibility. // -// ResourceService is the controller's primary surface for managing resources -// declaratively. From Phase 2 it operates synchronously per the -// no-op-on-existing semantics; Phase 3 adds async dispatch via OperationService -// for long-running applies (Cluster create, etc). -// -// Phase 2 supports atomic resources (VirtualMachine). Phase 4 adds composite -// resources (Cluster) which spawn child operations. +// ResourceService manages desired state and observed infrastructure. Apply +// and Delete submit asynchronous operations; migration RPCs synchronously +// transfer controller tracking without applying or deleting infrastructure. type ResourceServiceServer interface { - // Apply creates a resource. Phase 2 is no-op-on-existing: if a resource - // with the manifest's name already exists, the response carries its - // observed state without mutating it. + // Apply converges a resource, creating or updating infrastructure as needed. + // To adopt an existing VM without changing it, use AdoptResource instead. Apply(context.Context, *ApplyRequest) (*ApplyResponse, error) // Get returns the observed state of a single resource. Get(context.Context, *GetRequest) (*GetResponse, error) @@ -367,6 +396,12 @@ type ResourceServiceServer interface { // applied manifest so the UI can render them dim (Phase U9). Returns an // empty graph (root node only) for atomic resources. GetChildrenGraph(context.Context, *GetChildrenGraphRequest) (*GetChildrenGraphResponse, error) + // Export preserves ownership and packages desired intent with live identity. + ExportResource(context.Context, *ExportResourceRequest) (*ExportResourceResponse, error) + // Adopt tracks an existing standalone VM without changing the provider. + AdoptResource(context.Context, *AdoptResourceRequest) (*AdoptResourceResponse, error) + // Forget releases only controller tracking; the provider VM remains intact. + ForgetResource(context.Context, *ForgetResourceRequest) (*ForgetResourceResponse, error) mustEmbedUnimplementedResourceServiceServer() } @@ -404,6 +439,15 @@ func (UnimplementedResourceServiceServer) InvokeAction(context.Context, *InvokeA func (UnimplementedResourceServiceServer) GetChildrenGraph(context.Context, *GetChildrenGraphRequest) (*GetChildrenGraphResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetChildrenGraph not implemented") } +func (UnimplementedResourceServiceServer) ExportResource(context.Context, *ExportResourceRequest) (*ExportResourceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ExportResource not implemented") +} +func (UnimplementedResourceServiceServer) AdoptResource(context.Context, *AdoptResourceRequest) (*AdoptResourceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AdoptResource not implemented") +} +func (UnimplementedResourceServiceServer) ForgetResource(context.Context, *ForgetResourceRequest) (*ForgetResourceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ForgetResource not implemented") +} func (UnimplementedResourceServiceServer) mustEmbedUnimplementedResourceServiceServer() {} func (UnimplementedResourceServiceServer) testEmbeddedByValue() {} @@ -580,6 +624,60 @@ func _ResourceService_GetChildrenGraph_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _ResourceService_ExportResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ExportResourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceServiceServer).ExportResource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceService_ExportResource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceServiceServer).ExportResource(ctx, req.(*ExportResourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceService_AdoptResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AdoptResourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceServiceServer).AdoptResource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceService_AdoptResource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceServiceServer).AdoptResource(ctx, req.(*AdoptResourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ResourceService_ForgetResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ForgetResourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ResourceServiceServer).ForgetResource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ResourceService_ForgetResource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ResourceServiceServer).ForgetResource(ctx, req.(*ForgetResourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + // ResourceService_ServiceDesc is the grpc.ServiceDesc for ResourceService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -619,6 +717,18 @@ var ResourceService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetChildrenGraph", Handler: _ResourceService_GetChildrenGraph_Handler, }, + { + MethodName: "ExportResource", + Handler: _ResourceService_ExportResource_Handler, + }, + { + MethodName: "AdoptResource", + Handler: _ResourceService_AdoptResource_Handler, + }, + { + MethodName: "ForgetResource", + Handler: _ResourceService_ForgetResource_Handler, + }, }, Streams: []grpc.StreamDesc{ { From c8027ce81e9e296ea618bb072e8dfee1f26cabf3 Mon Sep 17 00:00:00 2001 From: Jeremy Harrington Date: Sun, 11 Oct 2026 12:55:40 -0700 Subject: [PATCH 2/2] style: group migration test imports consistently --- internal/controller/server/migration_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/controller/server/migration_test.go b/internal/controller/server/migration_test.go index cb093a1..202b3b1 100644 --- a/internal/controller/server/migration_test.go +++ b/internal/controller/server/migration_test.go @@ -12,6 +12,10 @@ import ( "strings" "testing" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "github.com/openctl/openctl/internal/controller/auth" "github.com/openctl/openctl/internal/controller/manifests" "github.com/openctl/openctl/internal/controller/operations" @@ -20,9 +24,6 @@ import ( "github.com/openctl/openctl/internal/controller/storage" apiv1 "github.com/openctl/openctl/pkg/api/v1" "github.com/openctl/openctl/pkg/protocol" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" ) func migrationVM() *protocol.Resource {