From 1ddc857fb5d81d5f863ab66cdf059cb3d109f728 Mon Sep 17 00:00:00 2001 From: Indra Gunanda Date: Sat, 8 Aug 2026 20:34:58 +0700 Subject: [PATCH 1/3] feat(paas): add UpdateComposeSource for existing compose services DeployCompose calls services.compose.createService first, so the panel rejects it with "Service already exists." before any content is written. There was no way to replace the compose content of a running stack over gRPC, only via the CLI's set-source-inline. UpdateComposeSource writes the content and redeploys, running the same gate as DeployCompose: invalid YAML and network_mode host are rejected, host-published ports are stripped, expose is kept. A missing service, or one that exists under a different type, maps to NotFound rather than a bare panel error, since the panel answers NOT_FOUND for both. skip_deploy stores the content without deploying so a caller can batch edits and deploy once. Also drop composeFile from the updateSourceInline payload. The panel's schema accepts only projectName, serviceName and content, and silently dropped the extra field, so both the RPC and the CLI were sending a field that never existed. services set-source-inline now takes 3 positional arguments; the old 4-argument form still works and warns that compose-file is ignored. --- CHANGELOG.md | 22 + README.md | 36 +- cmd/services.go | 28 +- docs/deployment-guide.md | 48 +- grpc_e2e_test.go | 120 +++++ internal/easypanel/client.go | 11 + internal/easypanel/version_test.go | 18 + internal/server/paas.go | 48 +- internal/server/panel_fake_test.go | 9 +- internal/server/updatecomposesource_test.go | 164 +++++++ proto/paas.pb.go | 508 +++++++++++++------- proto/paas.proto | 17 + proto/paas_grpc.pb.go | 72 ++- 13 files changed, 882 insertions(+), 219 deletions(-) create mode 100644 internal/server/updatecomposesource_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 71320ac..b46c8ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project are documented here. Versions follow the `vMAJOR.MINOR.PATCH` git tags that drive the release pipeline. +## Unreleased + +### Features + +- New RPC `UpdateComposeSource` replaces the compose content of an existing + compose service and redeploys it. `DeployCompose` calls the panel's + `createService` first, so it fails with `Service already exists.` on a service + that is already there, leaving no way to update a live stack over gRPC. The new + RPC runs the same gate as `DeployCompose` (invalid YAML and `network_mode: host` + rejected, host `ports:` stripped, `expose:` kept), maps a missing or + wrong-typed service to `NotFound`, and accepts `skip_deploy` to store content + without deploying so several edits can be batched into one deploy. + +### Fixes + +- Stop sending `composeFile` to `services.compose.updateSourceInline`. The panel's + schema accepts only `projectName`, `serviceName` and `content`, and silently + dropped the extra field. Affected `DeployCompose` and the CLI. +- `services set-source-inline` now takes 3 arguments + (`[project] [service] [compose-content]`). The old 4-argument form is still + accepted and warns that the `compose-file` argument is ignored. + ## v0.1.0 Supported Easypanel versions: **2.32.0 .. 2.33.1**, both bounds backed by pinned diff --git a/README.md b/README.md index 0798eb2..c1b211a 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A Go toolkit for managing [Easypanel](https://easypanel.io) deployments — both ┌──────────────────▼──────────────────────────────────┐ │ gRPC PaaS Server (cmd/grpc-server) │ │ • Auth interceptor (constant-time token compare) │ -│ • 16 RPC methods │ +│ • 17 RPC methods │ │ • Compose port sanitization │ │ • Auto-domain provisioning │ └──────────────────┬──────────────────────────────────┘ @@ -31,7 +31,7 @@ A Go toolkit for managing [Easypanel](https://easypanel.io) deployments — both - **CLI** — Direct Easypanel tRPC control (projects, services, domains, users, certs, settings) - **gRPC Server** — Multi-tenant PaaS adapter with bearer token authentication - **Container Deployments** — Deploy Docker images with auto-assigned domains -- **Compose Stacks** — Deploy docker-compose with per-service domain routing (ports auto-stripped for security) +- **Compose Stacks** — Deploy docker-compose with per-service domain routing (ports auto-stripped for security), and replace the content of a live stack in place - **Domain Management** — Default subdomains + custom domains with HTTPS - **Lifecycle Control** — Start, stop, restart, scale, update env/resources - **Resource Limits** — CPU and memory constraints per service @@ -118,7 +118,7 @@ export EASYPANEL_ENDPOINT EASYPANEL_API_KEY ./deploy-everything services set-dockerfile '' # Compose sources (save, then automatically redeploy) -./deploy-everything services set-source-inline docker-compose.yml '' +./deploy-everything services set-source-inline '' ./deploy-everything services set-source-git-compose docker-compose.yml [--root-path /] # Logs @@ -180,7 +180,8 @@ Tokens are configured via `GRPC_AUTH_TOKENS` (comma-separated). Tokens grant pan | Method | Description | |--------|-------------| | `DeployContainer` | Deploy Docker image → create service + set image + env + deploy + auto-domain | -| `DeployCompose` | Deploy docker-compose stack (strips `ports:`, routes via `composeService`) | +| `DeployCompose` | Create and deploy a docker-compose stack (strips `ports:`, routes via `composeService`). Fails if the service already exists | +| `UpdateComposeSource` | Replace the compose content of an existing compose service, then redeploy. Set `skip_deploy` to store without deploying | | `DestroyService` | Remove service and all associated domains | | `GetServiceStatus` | Service config (name, type, image, env, domains, deploy URL) plus runtime state (`status`, running containers, compose sub-services). Env values are redacted unless `include_env` is set | | `ListServices` | All services in a project | @@ -233,6 +234,33 @@ grpcurl -plaintext -H "authorization: your-token" -d '{ }' localhost:50051 paas.PaaS/DeployCompose ``` +#### Update an existing compose stack + +`DeployCompose` creates: it calls the panel's `createService` first, so it fails with +`create compose: [BAD_REQUEST] Service already exists.` on a service that is already +there. Use `UpdateComposeSource` to replace the content of a live stack. + +```bash +grpcurl -plaintext -H "authorization: your-token" -d '{ + "project": "myproject", + "service": "stack", + "composeContent": "services:\n web:\n image: wordpress:6.5\n expose:\n - \"80\"\n" +}' localhost:50051 paas.PaaS/UpdateComposeSource +``` + +```json +{ + "deployed": true, + "status": "deploying" +} +``` + +The content fully replaces what was stored, and goes through the same gate as +`DeployCompose`: invalid YAML and `network_mode: host` are rejected, host-published +`ports:` are stripped, `expose:` is kept. Set `"skipDeploy": true` to store the +content without redeploying, then deploy once after a batch of edits. A service that +does not exist, or exists under another type, returns `NotFound`. + #### Add a custom domain ```bash diff --git a/cmd/services.go b/cmd/services.go index e528643..f2d8696 100644 --- a/cmd/services.go +++ b/cmd/services.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "os" "sort" "strings" @@ -678,26 +679,37 @@ func rawJSONText(raw json.RawMessage) string { // --- Compose-specific --- var servicesSetSourceInlineCmd = &cobra.Command{ - Use: "set-source-inline [project] [service] [compose-file] [compose-content]", + Use: "set-source-inline [project] [service] [compose-content]", Short: "Set inline compose content (compose type)", - Args: cobra.ExactArgs(4), + Long: "Set inline compose content (compose type).\n\n" + + "A 4-argument form [project] [service] [compose-file] [compose-content] is still\n" + + "accepted for compatibility. The panel schema has no compose-file field, so that\n" + + "argument is ignored.", + Args: cobra.RangeArgs(3, 4), RunE: func(cmd *cobra.Command, args []string) error { + project, service := args[0], args[1] + content := args[2] + if len(args) == 4 { + // Legacy positional compose-file, dropped by the panel's zod schema. + content = args[3] + fmt.Fprintln(os.Stderr, "warning: the compose-file argument is ignored; the panel accepts only projectName, serviceName and content") + } + c := newPanelClient() input := map[string]any{ - "projectName": args[0], - "serviceName": args[1], - "composeFile": args[2], + "projectName": project, + "serviceName": service, // Panel schema calls this field "content", not "composeContent". - "content": args[3], + "content": content, } err := c.call(context.Background(), "/api/trpc/services.compose.updateSourceInline", input, nil) if err != nil { return err } - if err := redeployAfterConfig(c, args[0], args[1], "compose"); err != nil { + if err := redeployAfterConfig(c, project, service, "compose"); err != nil { return fmt.Errorf("apply inline source: %w", err) } - fmt.Printf("Inline source set and deployed: %s/%s\n", args[0], args[1]) + fmt.Printf("Inline source set and deployed: %s/%s\n", project, service) return nil }, } diff --git a/docs/deployment-guide.md b/docs/deployment-guide.md index 155f797..3e76980 100644 --- a/docs/deployment-guide.md +++ b/docs/deployment-guide.md @@ -240,6 +240,48 @@ Without `customDomain`, generated host format is: {project}-{service}-{composeService}.{DEFAULT_DOMAIN} ``` +### Update an existing Compose stack + +`DeployCompose` creates. It calls the panel's `createService` first, so a second call +for the same service fails: + +```text +create compose: [BAD_REQUEST] Service already exists. +``` + +Use `UpdateComposeSource` to replace the content of a stack that is already running: + +```bash +jq -n --rawfile compose docker-compose.yml '{ + project: "demo", + service: "whoami-stack", + composeContent: $compose +}' > /tmp/update-compose.json + +paas_call UpdateComposeSource < /tmp/update-compose.json +``` + +```json +{ + "deployed": true, + "status": "deploying" +} +``` + +| Field | Meaning | +|-------|---------| +| `composeContent` | Full replacement. Not merged with the stored content | +| `skipDeploy` | Store the content without redeploying, to batch several edits | +| `deployed` | `false` when `skipDeploy` was set | + +The Compose rules above still apply: invalid YAML and `network_mode: host` are +rejected, host `ports:` are stripped, `expose:` survives. A service that does not +exist, or exists under a different type, returns `NotFound`. + +Domains are not touched by this RPC. Use `AddDomain` / `RemoveDomain` for routing +changes, and note that adding a domain to a Compose stack needs a redeploy to take +effect. + ### Multi-service WordPress + MySQL + Adminer ```yaml @@ -687,7 +729,7 @@ services: ```bash $CLI services create demo stack compose -$CLI services set-source-inline demo stack docker-compose.yml "$(cat docker-compose.yml)" +$CLI services set-source-inline demo stack "$(cat docker-compose.yml)" $CLI domains create demo stack stack.example.com \ --type compose \ --compose-service web \ @@ -697,6 +739,10 @@ $CLI domains create demo stack stack.example.com \ Both source and domain commands automatically redeploy. Direct CLI Compose source is sent to Easypanel as written, so use `expose` rather than host `ports`. +Re-running `set-source-inline` on the same service replaces the stored content, so +this is also the update path. The gRPC equivalent is +[`UpdateComposeSource`](#update-an-existing-compose-stack). + ### Compose from Git ```bash diff --git a/grpc_e2e_test.go b/grpc_e2e_test.go index bc500e0..010b5d3 100644 --- a/grpc_e2e_test.go +++ b/grpc_e2e_test.go @@ -548,3 +548,123 @@ func TestGRPC_RuntimeStatusAndLogs(t *testing.T) { } t.Log(" ✓ env redacted by default, returned with include_env") } + +// TestGRPC_UpdateComposeSourceE2E covers the gap DeployCompose leaves: replacing +// the compose content of a service that already exists. DeployCompose calls +// createService first, which the panel rejects with "Service already exists." +func TestGRPC_UpdateComposeSourceE2E(t *testing.T) { + client, cleanup := startTestServer(t) + defer cleanup() + + ctx := authCtx() + project := "pods" + service := "update-src-e2e" + + client.DestroyService(ctx, &pb.DestroyServiceRequest{ + Project: project, Service: service, ServiceType: "compose", + }) + defer client.DestroyService(ctx, &pb.DestroyServiceRequest{ + Project: project, Service: service, ServiceType: "compose", + }) + + // A missing service must not look like a transport failure. + t.Log("Step 1: UpdateComposeSource on a service that does not exist") + _, err := client.UpdateComposeSource(ctx, &pb.UpdateComposeSourceRequest{ + Project: project, Service: service, + ComposeContent: "services:\n web:\n image: nginx:alpine\n", + }) + if grpcstatus.Code(err) != codes.NotFound { + t.Fatalf("want NotFound before the service exists, got %v", err) + } + t.Logf(" ✓ %v", err) + + t.Log("Step 2: DeployCompose to create it") + if _, err := client.DeployCompose(ctx, &pb.DeployComposeRequest{ + Project: project, Service: service, + ComposeContent: "services:\n web:\n image: nginx:alpine\n expose:\n - \"80\"\n", + }); err != nil { + t.Fatalf("DeployCompose: %v", err) + } + t.Log(" ✓ created") + + // DeployCompose cannot be reused to update: prove the failure this RPC fixes. + t.Log("Step 3: DeployCompose again must fail on the existing service") + if _, err := client.DeployCompose(ctx, &pb.DeployComposeRequest{ + Project: project, Service: service, + ComposeContent: "services:\n web:\n image: nginx:alpine\n", + }); err == nil { + t.Fatal("want DeployCompose to reject an existing service") + } else if !strings.Contains(err.Error(), "already exists") { + t.Fatalf("want \"already exists\", got %v", err) + } + t.Log(" ✓ rejected as expected") + + // Host ports must still be stripped, expose kept, content replaced verbatim. + updated := `services: + web: + image: httpd:alpine + ports: + - "8080:80" + expose: + - "80" + environment: + UPDATED_MARKER: update-src-e2e-ok +` + t.Log("Step 4: UpdateComposeSource replaces the content") + resp, err := client.UpdateComposeSource(ctx, &pb.UpdateComposeSourceRequest{ + Project: project, Service: service, ComposeContent: updated, + }) + if err != nil { + t.Fatalf("UpdateComposeSource: %v", err) + } + if !resp.Deployed { + t.Errorf("want deployed=true, got %v (status=%q)", resp.Deployed, resp.Status) + } + t.Logf(" ✓ deployed=%v status=%s", resp.Deployed, resp.Status) + + // GetServiceStatus does not expose compose source, so read it from the panel. + t.Log("Step 5: verify stored content") + stored := composeSource(t, project, service) + if !strings.Contains(stored, "update-src-e2e-ok") { + t.Errorf(" ✗ new content not stored, source=%q", stored) + } + if !strings.Contains(stored, "httpd:alpine") { + t.Errorf(" ✗ image not replaced, source=%q", stored) + } + if strings.Contains(stored, "8080:80") { + t.Errorf(" ✗ host ports survived sanitizing, source=%q", stored) + } + t.Log(" ✓ content replaced, host ports stripped") + + t.Log("Step 6: skipDeploy stores without deploying") + resp, err = client.UpdateComposeSource(ctx, &pb.UpdateComposeSourceRequest{ + Project: project, Service: service, + ComposeContent: "services:\n web:\n image: nginx:alpine\n expose:\n - \"80\"\n", + SkipDeploy: true, + }) + if err != nil { + t.Fatalf("UpdateComposeSource(skipDeploy): %v", err) + } + if resp.Deployed || resp.Status != "stored" { + t.Errorf("want deployed=false status=stored, got %v/%q", resp.Deployed, resp.Status) + } + t.Log(" ✓ stored without deploy") +} + +// composeSource reads the compose content the panel has stored for a service. +func composeSource(t *testing.T, project, service string) string { + t.Helper() + ep := easypanel.NewClient(os.Getenv("EASYPANEL_ENDPOINT"), os.Getenv("EASYPANEL_API_KEY")) + var resp easypanel.Response[struct { + Source struct { + Content string `json:"content"` + } `json:"source"` + }] + if err := ep.Call(context.Background(), easypanel.ComposeRoute(easypanel.ProcInspectService), map[string]string{ + "projectName": project, + "serviceName": service, + }, &resp); err != nil { + t.Fatalf("inspect compose source: %v", err) + } + return resp.JSON.Source.Content +} diff --git a/internal/easypanel/client.go b/internal/easypanel/client.go index 9de13f7..7d7c897 100644 --- a/internal/easypanel/client.go +++ b/internal/easypanel/client.go @@ -106,6 +106,17 @@ func IsLogStoreUnreachable(err error) bool { return false } +// IsNotFound reports whether err is the panel's NOT_FOUND. The panel returns it +// both for a missing service and for a service of a different type than the +// route's namespace, since each type only inspects its own services. +func IsNotFound(err error) bool { + var apiErr *APIError + if !errors.As(err, &apiErr) { + return false + } + return apiErr.Code == "NOT_FOUND" || apiErr.Status == 404 +} + // trpcErrorEnvelope is how the panel reports errors: nested under "json", with // zod field errors under data.zodErrors. type trpcErrorEnvelope struct { diff --git a/internal/easypanel/version_test.go b/internal/easypanel/version_test.go index bc8d190..49430e4 100644 --- a/internal/easypanel/version_test.go +++ b/internal/easypanel/version_test.go @@ -109,3 +109,21 @@ func TestIsLogStoreUnreachable(t *testing.T) { t.Error("plain errors must not be classified as panel log store failures") } } + +func TestIsNotFound(t *testing.T) { + // The panel answers NOT_FOUND for a missing service and for one whose type + // does not match the route namespace. + if !IsNotFound(error(&APIError{Code: "NOT_FOUND", Status: 404, Message: "Service not found."})) { + t.Error("want NOT_FOUND classified as not found") + } + // Status alone is enough: some proxies drop the code. + if !IsNotFound(error(&APIError{Status: 404, Message: "Service not found."})) { + t.Error("want status 404 classified as not found") + } + if IsNotFound(error(&APIError{Code: "BAD_REQUEST", Status: 400, Message: "Service already exists."})) { + t.Error("did not expect BAD_REQUEST classified as not found") + } + if IsNotFound(errors.New("Service not found.")) { + t.Error("plain errors must not be classified as panel NOT_FOUND") + } +} diff --git a/internal/server/paas.go b/internal/server/paas.go index a10dfdf..1af136b 100644 --- a/internal/server/paas.go +++ b/internal/server/paas.go @@ -216,7 +216,6 @@ func (s *PaaSServer) DeployCompose(ctx context.Context, req *pb.DeployComposeReq err = s.ep.Call(ctx, easypanel.ComposeRoute(easypanel.ProcUpdateSourceInline), map[string]any{ "projectName": req.Project, "serviceName": req.Service, - "composeFile": "docker-compose.yml", "content": content, }, nil) if err != nil { @@ -290,6 +289,53 @@ func (s *PaaSServer) DeployCompose(ctx context.Context, req *pb.DeployComposeReq }, nil } +// UpdateComposeSource replaces the compose content of a service that already +// exists. DeployCompose cannot do this: it calls createService first, which the +// panel rejects with "Service already exists." before any content is written. +func (s *PaaSServer) UpdateComposeSource(ctx context.Context, req *pb.UpdateComposeSourceRequest) (*pb.UpdateComposeSourceResponse, error) { + if req.Project == "" || req.Service == "" { + return nil, status.Error(codes.InvalidArgument, "project and service are required") + } + if strings.TrimSpace(req.ComposeContent) == "" { + return nil, status.Error(codes.InvalidArgument, "composeContent is required") + } + + // Same gate as DeployCompose: reject invalid YAML and host networking, and + // drop host-published ports, before anything reaches the panel. + content, err := sanitizeCompose(req.ComposeContent) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid compose YAML: %v", err) + } + + err = s.ep.Call(ctx, easypanel.ComposeRoute(easypanel.ProcUpdateSourceInline), map[string]any{ + "projectName": req.Project, + "serviceName": req.Service, + "content": content, + }, nil) + if err != nil { + // The panel answers NOT_FOUND both for a missing service and for one that + // is not a compose service, since each type only sees its own. + if easypanel.IsNotFound(err) { + return nil, status.Errorf(codes.NotFound, + "no compose service %q in project %q: use DeployCompose to create it, and check the service type if it already exists", + req.Service, req.Project) + } + return nil, fmt.Errorf("set compose: %w", err) + } + + // Stored content only takes effect on deploy. Callers batching several edits + // can skip it and deploy once at the end. + if req.SkipDeploy { + return &pb.UpdateComposeSourceResponse{Deployed: false, Status: "stored"}, nil + } + + if err := s.redeployCompose(ctx, req.Project, req.Service); err != nil { + return nil, fmt.Errorf("deploy: %w", err) + } + + return &pb.UpdateComposeSourceResponse{Deployed: true, Status: "deploying"}, nil +} + func (s *PaaSServer) DestroyService(ctx context.Context, req *pb.DestroyServiceRequest) (*pb.DestroyServiceResponse, error) { svcType := req.ServiceType if svcType == "" { diff --git a/internal/server/panel_fake_test.go b/internal/server/panel_fake_test.go index 1611168..1b81c73 100644 --- a/internal/server/panel_fake_test.go +++ b/internal/server/panel_fake_test.go @@ -5,6 +5,7 @@ import ( "io" "net/http" "net/http/httptest" + "strconv" "strings" "sync" "testing" @@ -56,9 +57,13 @@ func (p *fakePanel) handle(w http.ResponseWriter, r *http.Request) { return } if strings.HasPrefix(resp, "!") { - // "! " returns an error response. + // "! " returns an error response with that HTTP status. parts := strings.SplitN(strings.TrimPrefix(resp, "!"), " ", 2) - w.WriteHeader(http.StatusBadRequest) + code, err := strconv.Atoi(parts[0]) + if err != nil { + code = http.StatusBadRequest + } + w.WriteHeader(code) if len(parts) == 2 { _, _ = w.Write([]byte(parts[1])) } diff --git a/internal/server/updatecomposesource_test.go b/internal/server/updatecomposesource_test.go new file mode 100644 index 0000000..3f50c4f --- /dev/null +++ b/internal/server/updatecomposesource_test.go @@ -0,0 +1,164 @@ +package server + +import ( + "context" + "strings" + "testing" + + "github.com/igun997/deploy-everything/internal/easypanel" + pb "github.com/igun997/deploy-everything/proto" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var ( + routeComposeUpdateSource = easypanel.ComposeRoute(easypanel.ProcUpdateSourceInline) + routeComposeDeploy = easypanel.ComposeRoute(easypanel.ProcDeployService) +) + +const composeWithHostPorts = `services: + agent: + image: nginx + ports: + - "8080:80" + expose: + - "8642" +` + +func okPanel(t *testing.T) *fakePanel { + t.Helper() + return newFakePanel(t, map[string]string{ + routeComposeUpdateSource: `{"json":null}`, + routeComposeDeploy: `{"json":null}`, + }) +} + +func TestUpdateComposeSourceSendsOnlyPanelFieldsAndDeploys(t *testing.T) { + panel := okPanel(t) + + resp, err := panel.newServer().UpdateComposeSource(context.Background(), &pb.UpdateComposeSourceRequest{ + Project: "pods", Service: "hermes-a1", ComposeContent: composeWithHostPorts, + }) + if err != nil { + t.Fatalf("UpdateComposeSource: %v", err) + } + if !resp.Deployed || resp.Status != "deploying" { + t.Errorf("want deployed=true status=deploying, got %v/%q", resp.Deployed, resp.Status) + } + + in := panel.input(routeComposeUpdateSource, 0) + // The panel's zod schema accepts only these three fields. + if len(in) != 3 { + t.Errorf("want exactly 3 input fields, got %d: %v", len(in), in) + } + if _, ok := in["composeFile"]; ok { + t.Error("composeFile is not in the panel schema and must not be sent") + } + if in["projectName"] != "pods" || in["serviceName"] != "hermes-a1" { + t.Errorf("wrong identifiers: %v", in) + } + + content, _ := in["content"].(string) + if strings.Contains(content, "8080:80") { + t.Error("host-published ports must be stripped before reaching the panel") + } + if !strings.Contains(content, "8642") { + t.Error("expose must survive sanitizing") + } + + if panel.callCount(routeComposeDeploy) != 1 { + t.Errorf("want 1 deploy call, got %d", panel.callCount(routeComposeDeploy)) + } +} + +func TestUpdateComposeSourceSkipDeploy(t *testing.T) { + panel := okPanel(t) + + resp, err := panel.newServer().UpdateComposeSource(context.Background(), &pb.UpdateComposeSourceRequest{ + Project: "pods", Service: "hermes-a1", ComposeContent: composeWithHostPorts, SkipDeploy: true, + }) + if err != nil { + t.Fatalf("UpdateComposeSource: %v", err) + } + if resp.Deployed || resp.Status != "stored" { + t.Errorf("want deployed=false status=stored, got %v/%q", resp.Deployed, resp.Status) + } + if panel.callCount(routeComposeUpdateSource) != 1 { + t.Error("content must still be written when the deploy is skipped") + } + if got := panel.callCount(routeComposeDeploy); got != 0 { + t.Errorf("skipDeploy must not deploy, got %d deploy calls", got) + } +} + +func TestUpdateComposeSourceMapsMissingServiceToNotFound(t *testing.T) { + // The panel answers NOT_FOUND for a missing service and for a service of a + // different type, since each type only inspects its own. + panel := newFakePanel(t, map[string]string{ + routeComposeUpdateSource: `!404 {"json":{"code":"NOT_FOUND","status":404,"message":"Service not found."}}`, + }) + + _, err := panel.newServer().UpdateComposeSource(context.Background(), &pb.UpdateComposeSourceRequest{ + Project: "pods", Service: "ghost", ComposeContent: composeWithHostPorts, + }) + if err == nil { + t.Fatal("want an error for a service that does not exist") + } + if got := status.Code(err); got != codes.NotFound { + t.Errorf("want NotFound, got %s: %v", got, err) + } + if !strings.Contains(err.Error(), "DeployCompose") { + t.Errorf("the error should point at the RPC that creates the service, got %q", err.Error()) + } + if panel.callCount(routeComposeDeploy) != 0 { + t.Error("a failed content write must not be followed by a deploy") + } +} + +func TestUpdateComposeSourceRejectsBadInput(t *testing.T) { + cases := []struct { + name string + req *pb.UpdateComposeSourceRequest + }{ + {"no project", &pb.UpdateComposeSourceRequest{Service: "a", ComposeContent: composeWithHostPorts}}, + {"no service", &pb.UpdateComposeSourceRequest{Project: "p", ComposeContent: composeWithHostPorts}}, + {"empty content", &pb.UpdateComposeSourceRequest{Project: "p", Service: "a", ComposeContent: " \n"}}, + {"not yaml", &pb.UpdateComposeSourceRequest{Project: "p", Service: "a", ComposeContent: "\tnot: [yaml"}}, + {"no services key", &pb.UpdateComposeSourceRequest{Project: "p", Service: "a", ComposeContent: "version: \"3\"\n"}}, + {"host networking", &pb.UpdateComposeSourceRequest{Project: "p", Service: "a", + ComposeContent: "services:\n a:\n image: nginx\n network_mode: host\n"}}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + panel := okPanel(t) + _, err := panel.newServer().UpdateComposeSource(context.Background(), tc.req) + if err == nil { + t.Fatal("want InvalidArgument") + } + if got := status.Code(err); got != codes.InvalidArgument { + t.Errorf("want InvalidArgument, got %s: %v", got, err) + } + if panel.callCount(routeComposeUpdateSource) != 0 { + t.Error("invalid input must be rejected before the panel is called") + } + }) + } +} + +func TestUpdateComposeSourceReportsDeployFailure(t *testing.T) { + panel := newFakePanel(t, map[string]string{ + routeComposeUpdateSource: `{"json":null}`, + routeComposeDeploy: `!400 {"json":{"code":"BAD_REQUEST","status":400,"message":"deploy is already running"}}`, + }) + + _, err := panel.newServer().UpdateComposeSource(context.Background(), &pb.UpdateComposeSourceRequest{ + Project: "pods", Service: "hermes-a1", ComposeContent: composeWithHostPorts, + }) + if err == nil { + t.Fatal("want the deploy failure surfaced") + } + if !strings.Contains(err.Error(), "deploy") { + t.Errorf("want the deploy step named in the error, got %q", err.Error()) + } +} diff --git a/proto/paas.pb.go b/proto/paas.pb.go index 2b7bc58..8e67dba 100644 --- a/proto/paas.pb.go +++ b/proto/paas.pb.go @@ -445,6 +445,126 @@ func (x *DomainMapping) GetPort() int32 { return 0 } +type UpdateComposeSourceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` + Service string `protobuf:"bytes,2,opt,name=service,proto3" json:"service,omitempty"` + ComposeContent string `protobuf:"bytes,3,opt,name=compose_content,json=composeContent,proto3" json:"compose_content,omitempty"` // full replacement docker-compose.yml content + SkipDeploy bool `protobuf:"varint,4,opt,name=skip_deploy,json=skipDeploy,proto3" json:"skip_deploy,omitempty"` // store the new content without redeploying + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateComposeSourceRequest) Reset() { + *x = UpdateComposeSourceRequest{} + mi := &file_proto_paas_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateComposeSourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateComposeSourceRequest) ProtoMessage() {} + +func (x *UpdateComposeSourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_paas_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 UpdateComposeSourceRequest.ProtoReflect.Descriptor instead. +func (*UpdateComposeSourceRequest) Descriptor() ([]byte, []int) { + return file_proto_paas_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdateComposeSourceRequest) GetProject() string { + if x != nil { + return x.Project + } + return "" +} + +func (x *UpdateComposeSourceRequest) GetService() string { + if x != nil { + return x.Service + } + return "" +} + +func (x *UpdateComposeSourceRequest) GetComposeContent() string { + if x != nil { + return x.ComposeContent + } + return "" +} + +func (x *UpdateComposeSourceRequest) GetSkipDeploy() bool { + if x != nil { + return x.SkipDeploy + } + return false +} + +type UpdateComposeSourceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Deployed bool `protobuf:"varint,1,opt,name=deployed,proto3" json:"deployed,omitempty"` // false when skip_deploy was set + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateComposeSourceResponse) Reset() { + *x = UpdateComposeSourceResponse{} + mi := &file_proto_paas_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateComposeSourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateComposeSourceResponse) ProtoMessage() {} + +func (x *UpdateComposeSourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_paas_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 UpdateComposeSourceResponse.ProtoReflect.Descriptor instead. +func (*UpdateComposeSourceResponse) Descriptor() ([]byte, []int) { + return file_proto_paas_proto_rawDescGZIP(), []int{7} +} + +func (x *UpdateComposeSourceResponse) GetDeployed() bool { + if x != nil { + return x.Deployed + } + return false +} + +func (x *UpdateComposeSourceResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + type DestroyServiceRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Project string `protobuf:"bytes,1,opt,name=project,proto3" json:"project,omitempty"` @@ -456,7 +576,7 @@ type DestroyServiceRequest struct { func (x *DestroyServiceRequest) Reset() { *x = DestroyServiceRequest{} - mi := &file_proto_paas_proto_msgTypes[6] + mi := &file_proto_paas_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -468,7 +588,7 @@ func (x *DestroyServiceRequest) String() string { func (*DestroyServiceRequest) ProtoMessage() {} func (x *DestroyServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[6] + mi := &file_proto_paas_proto_msgTypes[8] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -481,7 +601,7 @@ func (x *DestroyServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DestroyServiceRequest.ProtoReflect.Descriptor instead. func (*DestroyServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{6} + return file_proto_paas_proto_rawDescGZIP(), []int{8} } func (x *DestroyServiceRequest) GetProject() string { @@ -514,7 +634,7 @@ type DestroyServiceResponse struct { func (x *DestroyServiceResponse) Reset() { *x = DestroyServiceResponse{} - mi := &file_proto_paas_proto_msgTypes[7] + mi := &file_proto_paas_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -526,7 +646,7 @@ func (x *DestroyServiceResponse) String() string { func (*DestroyServiceResponse) ProtoMessage() {} func (x *DestroyServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[7] + mi := &file_proto_paas_proto_msgTypes[9] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -539,7 +659,7 @@ func (x *DestroyServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DestroyServiceResponse.ProtoReflect.Descriptor instead. func (*DestroyServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{7} + return file_proto_paas_proto_rawDescGZIP(), []int{9} } func (x *DestroyServiceResponse) GetSuccess() bool { @@ -564,7 +684,7 @@ type GetServiceStatusRequest struct { func (x *GetServiceStatusRequest) Reset() { *x = GetServiceStatusRequest{} - mi := &file_proto_paas_proto_msgTypes[8] + mi := &file_proto_paas_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -576,7 +696,7 @@ func (x *GetServiceStatusRequest) String() string { func (*GetServiceStatusRequest) ProtoMessage() {} func (x *GetServiceStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[8] + mi := &file_proto_paas_proto_msgTypes[10] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -589,7 +709,7 @@ func (x *GetServiceStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetServiceStatusRequest.ProtoReflect.Descriptor instead. func (*GetServiceStatusRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{8} + return file_proto_paas_proto_rawDescGZIP(), []int{10} } func (x *GetServiceStatusRequest) GetProject() string { @@ -644,7 +764,7 @@ type GetServiceStatusResponse struct { func (x *GetServiceStatusResponse) Reset() { *x = GetServiceStatusResponse{} - mi := &file_proto_paas_proto_msgTypes[9] + mi := &file_proto_paas_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -656,7 +776,7 @@ func (x *GetServiceStatusResponse) String() string { func (*GetServiceStatusResponse) ProtoMessage() {} func (x *GetServiceStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[9] + mi := &file_proto_paas_proto_msgTypes[11] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -669,7 +789,7 @@ func (x *GetServiceStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetServiceStatusResponse.ProtoReflect.Descriptor instead. func (*GetServiceStatusResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{9} + return file_proto_paas_proto_rawDescGZIP(), []int{11} } func (x *GetServiceStatusResponse) GetName() string { @@ -766,7 +886,7 @@ type ContainerStatus struct { func (x *ContainerStatus) Reset() { *x = ContainerStatus{} - mi := &file_proto_paas_proto_msgTypes[10] + mi := &file_proto_paas_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -778,7 +898,7 @@ func (x *ContainerStatus) String() string { func (*ContainerStatus) ProtoMessage() {} func (x *ContainerStatus) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[10] + mi := &file_proto_paas_proto_msgTypes[12] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -791,7 +911,7 @@ func (x *ContainerStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use ContainerStatus.ProtoReflect.Descriptor instead. func (*ContainerStatus) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{10} + return file_proto_paas_proto_rawDescGZIP(), []int{12} } func (x *ContainerStatus) GetId() string { @@ -845,7 +965,7 @@ type ListServicesRequest struct { func (x *ListServicesRequest) Reset() { *x = ListServicesRequest{} - mi := &file_proto_paas_proto_msgTypes[11] + mi := &file_proto_paas_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -857,7 +977,7 @@ func (x *ListServicesRequest) String() string { func (*ListServicesRequest) ProtoMessage() {} func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[11] + mi := &file_proto_paas_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -870,7 +990,7 @@ func (x *ListServicesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListServicesRequest.ProtoReflect.Descriptor instead. func (*ListServicesRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{11} + return file_proto_paas_proto_rawDescGZIP(), []int{13} } func (x *ListServicesRequest) GetProject() string { @@ -889,7 +1009,7 @@ type ListServicesResponse struct { func (x *ListServicesResponse) Reset() { *x = ListServicesResponse{} - mi := &file_proto_paas_proto_msgTypes[12] + mi := &file_proto_paas_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -901,7 +1021,7 @@ func (x *ListServicesResponse) String() string { func (*ListServicesResponse) ProtoMessage() {} func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[12] + mi := &file_proto_paas_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -914,7 +1034,7 @@ func (x *ListServicesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListServicesResponse.ProtoReflect.Descriptor instead. func (*ListServicesResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{12} + return file_proto_paas_proto_rawDescGZIP(), []int{14} } func (x *ListServicesResponse) GetServices() []*ServiceInfo { @@ -936,7 +1056,7 @@ type ServiceInfo struct { func (x *ServiceInfo) Reset() { *x = ServiceInfo{} - mi := &file_proto_paas_proto_msgTypes[13] + mi := &file_proto_paas_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -948,7 +1068,7 @@ func (x *ServiceInfo) String() string { func (*ServiceInfo) ProtoMessage() {} func (x *ServiceInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[13] + mi := &file_proto_paas_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -961,7 +1081,7 @@ func (x *ServiceInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ServiceInfo.ProtoReflect.Descriptor instead. func (*ServiceInfo) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{13} + return file_proto_paas_proto_rawDescGZIP(), []int{15} } func (x *ServiceInfo) GetName() string { @@ -1008,7 +1128,7 @@ type AddDomainRequest struct { func (x *AddDomainRequest) Reset() { *x = AddDomainRequest{} - mi := &file_proto_paas_proto_msgTypes[14] + mi := &file_proto_paas_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1020,7 +1140,7 @@ func (x *AddDomainRequest) String() string { func (*AddDomainRequest) ProtoMessage() {} func (x *AddDomainRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[14] + mi := &file_proto_paas_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1033,7 +1153,7 @@ func (x *AddDomainRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDomainRequest.ProtoReflect.Descriptor instead. func (*AddDomainRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{14} + return file_proto_paas_proto_rawDescGZIP(), []int{16} } func (x *AddDomainRequest) GetProject() string { @@ -1095,7 +1215,7 @@ type AddDomainResponse struct { func (x *AddDomainResponse) Reset() { *x = AddDomainResponse{} - mi := &file_proto_paas_proto_msgTypes[15] + mi := &file_proto_paas_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1107,7 +1227,7 @@ func (x *AddDomainResponse) String() string { func (*AddDomainResponse) ProtoMessage() {} func (x *AddDomainResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[15] + mi := &file_proto_paas_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1120,7 +1240,7 @@ func (x *AddDomainResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddDomainResponse.ProtoReflect.Descriptor instead. func (*AddDomainResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{15} + return file_proto_paas_proto_rawDescGZIP(), []int{17} } func (x *AddDomainResponse) GetDomainId() string { @@ -1150,7 +1270,7 @@ type RemoveDomainRequest struct { func (x *RemoveDomainRequest) Reset() { *x = RemoveDomainRequest{} - mi := &file_proto_paas_proto_msgTypes[16] + mi := &file_proto_paas_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1162,7 +1282,7 @@ func (x *RemoveDomainRequest) String() string { func (*RemoveDomainRequest) ProtoMessage() {} func (x *RemoveDomainRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[16] + mi := &file_proto_paas_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1175,7 +1295,7 @@ func (x *RemoveDomainRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveDomainRequest.ProtoReflect.Descriptor instead. func (*RemoveDomainRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{16} + return file_proto_paas_proto_rawDescGZIP(), []int{18} } func (x *RemoveDomainRequest) GetDomainId() string { @@ -1215,7 +1335,7 @@ type RemoveDomainResponse struct { func (x *RemoveDomainResponse) Reset() { *x = RemoveDomainResponse{} - mi := &file_proto_paas_proto_msgTypes[17] + mi := &file_proto_paas_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1227,7 +1347,7 @@ func (x *RemoveDomainResponse) String() string { func (*RemoveDomainResponse) ProtoMessage() {} func (x *RemoveDomainResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[17] + mi := &file_proto_paas_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1240,7 +1360,7 @@ func (x *RemoveDomainResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveDomainResponse.ProtoReflect.Descriptor instead. func (*RemoveDomainResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{17} + return file_proto_paas_proto_rawDescGZIP(), []int{19} } func (x *RemoveDomainResponse) GetSuccess() bool { @@ -1260,7 +1380,7 @@ type ListDomainsRequest struct { func (x *ListDomainsRequest) Reset() { *x = ListDomainsRequest{} - mi := &file_proto_paas_proto_msgTypes[18] + mi := &file_proto_paas_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1272,7 +1392,7 @@ func (x *ListDomainsRequest) String() string { func (*ListDomainsRequest) ProtoMessage() {} func (x *ListDomainsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[18] + mi := &file_proto_paas_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1285,7 +1405,7 @@ func (x *ListDomainsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDomainsRequest.ProtoReflect.Descriptor instead. func (*ListDomainsRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{18} + return file_proto_paas_proto_rawDescGZIP(), []int{20} } func (x *ListDomainsRequest) GetProject() string { @@ -1311,7 +1431,7 @@ type ListDomainsResponse struct { func (x *ListDomainsResponse) Reset() { *x = ListDomainsResponse{} - mi := &file_proto_paas_proto_msgTypes[19] + mi := &file_proto_paas_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1323,7 +1443,7 @@ func (x *ListDomainsResponse) String() string { func (*ListDomainsResponse) ProtoMessage() {} func (x *ListDomainsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[19] + mi := &file_proto_paas_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1336,7 +1456,7 @@ func (x *ListDomainsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListDomainsResponse.ProtoReflect.Descriptor instead. func (*ListDomainsResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{19} + return file_proto_paas_proto_rawDescGZIP(), []int{21} } func (x *ListDomainsResponse) GetDomains() []*DomainInfo { @@ -1360,7 +1480,7 @@ type DomainInfo struct { func (x *DomainInfo) Reset() { *x = DomainInfo{} - mi := &file_proto_paas_proto_msgTypes[20] + mi := &file_proto_paas_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1372,7 +1492,7 @@ func (x *DomainInfo) String() string { func (*DomainInfo) ProtoMessage() {} func (x *DomainInfo) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[20] + mi := &file_proto_paas_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1385,7 +1505,7 @@ func (x *DomainInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use DomainInfo.ProtoReflect.Descriptor instead. func (*DomainInfo) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{20} + return file_proto_paas_proto_rawDescGZIP(), []int{22} } func (x *DomainInfo) GetId() string { @@ -1442,7 +1562,7 @@ type UpdateEnvRequest struct { func (x *UpdateEnvRequest) Reset() { *x = UpdateEnvRequest{} - mi := &file_proto_paas_proto_msgTypes[21] + mi := &file_proto_paas_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1454,7 +1574,7 @@ func (x *UpdateEnvRequest) String() string { func (*UpdateEnvRequest) ProtoMessage() {} func (x *UpdateEnvRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[21] + mi := &file_proto_paas_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1467,7 +1587,7 @@ func (x *UpdateEnvRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateEnvRequest.ProtoReflect.Descriptor instead. func (*UpdateEnvRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{21} + return file_proto_paas_proto_rawDescGZIP(), []int{23} } func (x *UpdateEnvRequest) GetProject() string { @@ -1507,7 +1627,7 @@ type UpdateEnvResponse struct { func (x *UpdateEnvResponse) Reset() { *x = UpdateEnvResponse{} - mi := &file_proto_paas_proto_msgTypes[22] + mi := &file_proto_paas_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1519,7 +1639,7 @@ func (x *UpdateEnvResponse) String() string { func (*UpdateEnvResponse) ProtoMessage() {} func (x *UpdateEnvResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[22] + mi := &file_proto_paas_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1532,7 +1652,7 @@ func (x *UpdateEnvResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateEnvResponse.ProtoReflect.Descriptor instead. func (*UpdateEnvResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{22} + return file_proto_paas_proto_rawDescGZIP(), []int{24} } func (x *UpdateEnvResponse) GetSuccess() bool { @@ -1553,7 +1673,7 @@ type RestartServiceRequest struct { func (x *RestartServiceRequest) Reset() { *x = RestartServiceRequest{} - mi := &file_proto_paas_proto_msgTypes[23] + mi := &file_proto_paas_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1565,7 +1685,7 @@ func (x *RestartServiceRequest) String() string { func (*RestartServiceRequest) ProtoMessage() {} func (x *RestartServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[23] + mi := &file_proto_paas_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1578,7 +1698,7 @@ func (x *RestartServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartServiceRequest.ProtoReflect.Descriptor instead. func (*RestartServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{23} + return file_proto_paas_proto_rawDescGZIP(), []int{25} } func (x *RestartServiceRequest) GetProject() string { @@ -1611,7 +1731,7 @@ type RestartServiceResponse struct { func (x *RestartServiceResponse) Reset() { *x = RestartServiceResponse{} - mi := &file_proto_paas_proto_msgTypes[24] + mi := &file_proto_paas_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1623,7 +1743,7 @@ func (x *RestartServiceResponse) String() string { func (*RestartServiceResponse) ProtoMessage() {} func (x *RestartServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[24] + mi := &file_proto_paas_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1636,7 +1756,7 @@ func (x *RestartServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RestartServiceResponse.ProtoReflect.Descriptor instead. func (*RestartServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{24} + return file_proto_paas_proto_rawDescGZIP(), []int{26} } func (x *RestartServiceResponse) GetSuccess() bool { @@ -1657,7 +1777,7 @@ type StopServiceRequest struct { func (x *StopServiceRequest) Reset() { *x = StopServiceRequest{} - mi := &file_proto_paas_proto_msgTypes[25] + mi := &file_proto_paas_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1669,7 +1789,7 @@ func (x *StopServiceRequest) String() string { func (*StopServiceRequest) ProtoMessage() {} func (x *StopServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[25] + mi := &file_proto_paas_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1682,7 +1802,7 @@ func (x *StopServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StopServiceRequest.ProtoReflect.Descriptor instead. func (*StopServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{25} + return file_proto_paas_proto_rawDescGZIP(), []int{27} } func (x *StopServiceRequest) GetProject() string { @@ -1715,7 +1835,7 @@ type StopServiceResponse struct { func (x *StopServiceResponse) Reset() { *x = StopServiceResponse{} - mi := &file_proto_paas_proto_msgTypes[26] + mi := &file_proto_paas_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1727,7 +1847,7 @@ func (x *StopServiceResponse) String() string { func (*StopServiceResponse) ProtoMessage() {} func (x *StopServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[26] + mi := &file_proto_paas_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1740,7 +1860,7 @@ func (x *StopServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StopServiceResponse.ProtoReflect.Descriptor instead. func (*StopServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{26} + return file_proto_paas_proto_rawDescGZIP(), []int{28} } func (x *StopServiceResponse) GetSuccess() bool { @@ -1761,7 +1881,7 @@ type StartServiceRequest struct { func (x *StartServiceRequest) Reset() { *x = StartServiceRequest{} - mi := &file_proto_paas_proto_msgTypes[27] + mi := &file_proto_paas_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1773,7 +1893,7 @@ func (x *StartServiceRequest) String() string { func (*StartServiceRequest) ProtoMessage() {} func (x *StartServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[27] + mi := &file_proto_paas_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1786,7 +1906,7 @@ func (x *StartServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StartServiceRequest.ProtoReflect.Descriptor instead. func (*StartServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{27} + return file_proto_paas_proto_rawDescGZIP(), []int{29} } func (x *StartServiceRequest) GetProject() string { @@ -1819,7 +1939,7 @@ type StartServiceResponse struct { func (x *StartServiceResponse) Reset() { *x = StartServiceResponse{} - mi := &file_proto_paas_proto_msgTypes[28] + mi := &file_proto_paas_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1831,7 +1951,7 @@ func (x *StartServiceResponse) String() string { func (*StartServiceResponse) ProtoMessage() {} func (x *StartServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[28] + mi := &file_proto_paas_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1844,7 +1964,7 @@ func (x *StartServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StartServiceResponse.ProtoReflect.Descriptor instead. func (*StartServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{28} + return file_proto_paas_proto_rawDescGZIP(), []int{30} } func (x *StartServiceResponse) GetSuccess() bool { @@ -1881,7 +2001,7 @@ type GetLogsRequest struct { func (x *GetLogsRequest) Reset() { *x = GetLogsRequest{} - mi := &file_proto_paas_proto_msgTypes[29] + mi := &file_proto_paas_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1893,7 +2013,7 @@ func (x *GetLogsRequest) String() string { func (*GetLogsRequest) ProtoMessage() {} func (x *GetLogsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[29] + mi := &file_proto_paas_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1906,7 +2026,7 @@ func (x *GetLogsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLogsRequest.ProtoReflect.Descriptor instead. func (*GetLogsRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{29} + return file_proto_paas_proto_rawDescGZIP(), []int{31} } func (x *GetLogsRequest) GetProject() string { @@ -1989,7 +2109,7 @@ type GetLogsResponse struct { func (x *GetLogsResponse) Reset() { *x = GetLogsResponse{} - mi := &file_proto_paas_proto_msgTypes[30] + mi := &file_proto_paas_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2001,7 +2121,7 @@ func (x *GetLogsResponse) String() string { func (*GetLogsResponse) ProtoMessage() {} func (x *GetLogsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[30] + mi := &file_proto_paas_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2014,7 +2134,7 @@ func (x *GetLogsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLogsResponse.ProtoReflect.Descriptor instead. func (*GetLogsResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{30} + return file_proto_paas_proto_rawDescGZIP(), []int{32} } func (x *GetLogsResponse) GetLogs() string { @@ -2046,7 +2166,7 @@ type LogEntry struct { func (x *LogEntry) Reset() { *x = LogEntry{} - mi := &file_proto_paas_proto_msgTypes[31] + mi := &file_proto_paas_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2058,7 +2178,7 @@ func (x *LogEntry) String() string { func (*LogEntry) ProtoMessage() {} func (x *LogEntry) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[31] + mi := &file_proto_paas_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2071,7 +2191,7 @@ func (x *LogEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use LogEntry.ProtoReflect.Descriptor instead. func (*LogEntry) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{31} + return file_proto_paas_proto_rawDescGZIP(), []int{33} } func (x *LogEntry) GetTimestamp() string { @@ -2114,7 +2234,7 @@ type ScaleServiceRequest struct { func (x *ScaleServiceRequest) Reset() { *x = ScaleServiceRequest{} - mi := &file_proto_paas_proto_msgTypes[32] + mi := &file_proto_paas_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2126,7 +2246,7 @@ func (x *ScaleServiceRequest) String() string { func (*ScaleServiceRequest) ProtoMessage() {} func (x *ScaleServiceRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[32] + mi := &file_proto_paas_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2139,7 +2259,7 @@ func (x *ScaleServiceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ScaleServiceRequest.ProtoReflect.Descriptor instead. func (*ScaleServiceRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{32} + return file_proto_paas_proto_rawDescGZIP(), []int{34} } func (x *ScaleServiceRequest) GetProject() string { @@ -2179,7 +2299,7 @@ type ScaleServiceResponse struct { func (x *ScaleServiceResponse) Reset() { *x = ScaleServiceResponse{} - mi := &file_proto_paas_proto_msgTypes[33] + mi := &file_proto_paas_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2191,7 +2311,7 @@ func (x *ScaleServiceResponse) String() string { func (*ScaleServiceResponse) ProtoMessage() {} func (x *ScaleServiceResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[33] + mi := &file_proto_paas_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2204,7 +2324,7 @@ func (x *ScaleServiceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ScaleServiceResponse.ProtoReflect.Descriptor instead. func (*ScaleServiceResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{33} + return file_proto_paas_proto_rawDescGZIP(), []int{35} } func (x *ScaleServiceResponse) GetSuccess() bool { @@ -2226,7 +2346,7 @@ type ResourceLimits struct { func (x *ResourceLimits) Reset() { *x = ResourceLimits{} - mi := &file_proto_paas_proto_msgTypes[34] + mi := &file_proto_paas_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2238,7 +2358,7 @@ func (x *ResourceLimits) String() string { func (*ResourceLimits) ProtoMessage() {} func (x *ResourceLimits) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[34] + mi := &file_proto_paas_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2251,7 +2371,7 @@ func (x *ResourceLimits) ProtoReflect() protoreflect.Message { // Deprecated: Use ResourceLimits.ProtoReflect.Descriptor instead. func (*ResourceLimits) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{34} + return file_proto_paas_proto_rawDescGZIP(), []int{36} } func (x *ResourceLimits) GetCpuLimit() float64 { @@ -2294,7 +2414,7 @@ type UpdateResourcesRequest struct { func (x *UpdateResourcesRequest) Reset() { *x = UpdateResourcesRequest{} - mi := &file_proto_paas_proto_msgTypes[35] + mi := &file_proto_paas_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2306,7 +2426,7 @@ func (x *UpdateResourcesRequest) String() string { func (*UpdateResourcesRequest) ProtoMessage() {} func (x *UpdateResourcesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[35] + mi := &file_proto_paas_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2319,7 +2439,7 @@ func (x *UpdateResourcesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateResourcesRequest.ProtoReflect.Descriptor instead. func (*UpdateResourcesRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{35} + return file_proto_paas_proto_rawDescGZIP(), []int{37} } func (x *UpdateResourcesRequest) GetProject() string { @@ -2359,7 +2479,7 @@ type UpdateResourcesResponse struct { func (x *UpdateResourcesResponse) Reset() { *x = UpdateResourcesResponse{} - mi := &file_proto_paas_proto_msgTypes[36] + mi := &file_proto_paas_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2371,7 +2491,7 @@ func (x *UpdateResourcesResponse) String() string { func (*UpdateResourcesResponse) ProtoMessage() {} func (x *UpdateResourcesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[36] + mi := &file_proto_paas_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2384,7 +2504,7 @@ func (x *UpdateResourcesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateResourcesResponse.ProtoReflect.Descriptor instead. func (*UpdateResourcesResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{36} + return file_proto_paas_proto_rawDescGZIP(), []int{38} } func (x *UpdateResourcesResponse) GetSuccess() bool { @@ -2408,7 +2528,7 @@ type UpdateDeployRequest struct { func (x *UpdateDeployRequest) Reset() { *x = UpdateDeployRequest{} - mi := &file_proto_paas_proto_msgTypes[37] + mi := &file_proto_paas_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2420,7 +2540,7 @@ func (x *UpdateDeployRequest) String() string { func (*UpdateDeployRequest) ProtoMessage() {} func (x *UpdateDeployRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[37] + mi := &file_proto_paas_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2433,7 +2553,7 @@ func (x *UpdateDeployRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateDeployRequest.ProtoReflect.Descriptor instead. func (*UpdateDeployRequest) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{37} + return file_proto_paas_proto_rawDescGZIP(), []int{39} } func (x *UpdateDeployRequest) GetProject() string { @@ -2487,7 +2607,7 @@ type UpdateDeployResponse struct { func (x *UpdateDeployResponse) Reset() { *x = UpdateDeployResponse{} - mi := &file_proto_paas_proto_msgTypes[38] + mi := &file_proto_paas_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2499,7 +2619,7 @@ func (x *UpdateDeployResponse) String() string { func (*UpdateDeployResponse) ProtoMessage() {} func (x *UpdateDeployResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_paas_proto_msgTypes[38] + mi := &file_proto_paas_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2512,7 +2632,7 @@ func (x *UpdateDeployResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateDeployResponse.ProtoReflect.Descriptor instead. func (*UpdateDeployResponse) Descriptor() ([]byte, []int) { - return file_proto_paas_proto_rawDescGZIP(), []int{38} + return file_proto_paas_proto_rawDescGZIP(), []int{40} } func (x *UpdateDeployResponse) GetSuccess() bool { @@ -2567,7 +2687,16 @@ const file_proto_paas_proto_rawDesc = "" + "\rDomainMapping\x12'\n" + "\x0fcompose_service\x18\x01 \x01(\tR\x0ecomposeService\x12\x16\n" + "\x06domain\x18\x02 \x01(\tR\x06domain\x12\x12\n" + - "\x04port\x18\x03 \x01(\x05R\x04port\"n\n" + + "\x04port\x18\x03 \x01(\x05R\x04port\"\x9a\x01\n" + + "\x1aUpdateComposeSourceRequest\x12\x18\n" + + "\aproject\x18\x01 \x01(\tR\aproject\x12\x18\n" + + "\aservice\x18\x02 \x01(\tR\aservice\x12'\n" + + "\x0fcompose_content\x18\x03 \x01(\tR\x0ecomposeContent\x12\x1f\n" + + "\vskip_deploy\x18\x04 \x01(\bR\n" + + "skipDeploy\"Q\n" + + "\x1bUpdateComposeSourceResponse\x12\x1a\n" + + "\bdeployed\x18\x01 \x01(\bR\bdeployed\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\"n\n" + "\x15DestroyServiceRequest\x12\x18\n" + "\aproject\x18\x01 \x01(\tR\aproject\x12\x18\n" + "\aservice\x18\x02 \x01(\tR\aservice\x12!\n" + @@ -2718,10 +2847,11 @@ const file_proto_paas_proto_rawDesc = "" + "\rzero_downtime\x18\x05 \x01(\bR\fzeroDowntime\x12\x18\n" + "\acommand\x18\x06 \x01(\tR\acommand\"0\n" + "\x14UpdateDeployResponse\x12\x18\n" + - "\asuccess\x18\x01 \x01(\bR\asuccess2\xfc\b\n" + + "\asuccess\x18\x01 \x01(\bR\asuccess2\xd8\t\n" + "\x04PaaS\x12N\n" + "\x0fDeployContainer\x12\x1c.paas.DeployContainerRequest\x1a\x1d.paas.DeployContainerResponse\x12H\n" + - "\rDeployCompose\x12\x1a.paas.DeployComposeRequest\x1a\x1b.paas.DeployComposeResponse\x12K\n" + + "\rDeployCompose\x12\x1a.paas.DeployComposeRequest\x1a\x1b.paas.DeployComposeResponse\x12Z\n" + + "\x13UpdateComposeSource\x12 .paas.UpdateComposeSourceRequest\x1a!.paas.UpdateComposeSourceResponse\x12K\n" + "\x0eDestroyService\x12\x1b.paas.DestroyServiceRequest\x1a\x1c.paas.DestroyServiceResponse\x12Q\n" + "\x10GetServiceStatus\x12\x1d.paas.GetServiceStatusRequest\x1a\x1e.paas.GetServiceStatusResponse\x12E\n" + "\fListServices\x12\x19.paas.ListServicesRequest\x1a\x1a.paas.ListServicesResponse\x12<\n" + @@ -2749,97 +2879,101 @@ func file_proto_paas_proto_rawDescGZIP() []byte { return file_proto_paas_proto_rawDescData } -var file_proto_paas_proto_msgTypes = make([]protoimpl.MessageInfo, 42) +var file_proto_paas_proto_msgTypes = make([]protoimpl.MessageInfo, 44) var file_proto_paas_proto_goTypes = []any{ - (*DeployContainerRequest)(nil), // 0: paas.DeployContainerRequest - (*DeployContainerResponse)(nil), // 1: paas.DeployContainerResponse - (*DeployComposeRequest)(nil), // 2: paas.DeployComposeRequest - (*ComposeDomain)(nil), // 3: paas.ComposeDomain - (*DeployComposeResponse)(nil), // 4: paas.DeployComposeResponse - (*DomainMapping)(nil), // 5: paas.DomainMapping - (*DestroyServiceRequest)(nil), // 6: paas.DestroyServiceRequest - (*DestroyServiceResponse)(nil), // 7: paas.DestroyServiceResponse - (*GetServiceStatusRequest)(nil), // 8: paas.GetServiceStatusRequest - (*GetServiceStatusResponse)(nil), // 9: paas.GetServiceStatusResponse - (*ContainerStatus)(nil), // 10: paas.ContainerStatus - (*ListServicesRequest)(nil), // 11: paas.ListServicesRequest - (*ListServicesResponse)(nil), // 12: paas.ListServicesResponse - (*ServiceInfo)(nil), // 13: paas.ServiceInfo - (*AddDomainRequest)(nil), // 14: paas.AddDomainRequest - (*AddDomainResponse)(nil), // 15: paas.AddDomainResponse - (*RemoveDomainRequest)(nil), // 16: paas.RemoveDomainRequest - (*RemoveDomainResponse)(nil), // 17: paas.RemoveDomainResponse - (*ListDomainsRequest)(nil), // 18: paas.ListDomainsRequest - (*ListDomainsResponse)(nil), // 19: paas.ListDomainsResponse - (*DomainInfo)(nil), // 20: paas.DomainInfo - (*UpdateEnvRequest)(nil), // 21: paas.UpdateEnvRequest - (*UpdateEnvResponse)(nil), // 22: paas.UpdateEnvResponse - (*RestartServiceRequest)(nil), // 23: paas.RestartServiceRequest - (*RestartServiceResponse)(nil), // 24: paas.RestartServiceResponse - (*StopServiceRequest)(nil), // 25: paas.StopServiceRequest - (*StopServiceResponse)(nil), // 26: paas.StopServiceResponse - (*StartServiceRequest)(nil), // 27: paas.StartServiceRequest - (*StartServiceResponse)(nil), // 28: paas.StartServiceResponse - (*GetLogsRequest)(nil), // 29: paas.GetLogsRequest - (*GetLogsResponse)(nil), // 30: paas.GetLogsResponse - (*LogEntry)(nil), // 31: paas.LogEntry - (*ScaleServiceRequest)(nil), // 32: paas.ScaleServiceRequest - (*ScaleServiceResponse)(nil), // 33: paas.ScaleServiceResponse - (*ResourceLimits)(nil), // 34: paas.ResourceLimits - (*UpdateResourcesRequest)(nil), // 35: paas.UpdateResourcesRequest - (*UpdateResourcesResponse)(nil), // 36: paas.UpdateResourcesResponse - (*UpdateDeployRequest)(nil), // 37: paas.UpdateDeployRequest - (*UpdateDeployResponse)(nil), // 38: paas.UpdateDeployResponse - nil, // 39: paas.DeployContainerRequest.EnvEntry - nil, // 40: paas.DeployComposeRequest.EnvEntry - nil, // 41: paas.UpdateEnvRequest.EnvEntry + (*DeployContainerRequest)(nil), // 0: paas.DeployContainerRequest + (*DeployContainerResponse)(nil), // 1: paas.DeployContainerResponse + (*DeployComposeRequest)(nil), // 2: paas.DeployComposeRequest + (*ComposeDomain)(nil), // 3: paas.ComposeDomain + (*DeployComposeResponse)(nil), // 4: paas.DeployComposeResponse + (*DomainMapping)(nil), // 5: paas.DomainMapping + (*UpdateComposeSourceRequest)(nil), // 6: paas.UpdateComposeSourceRequest + (*UpdateComposeSourceResponse)(nil), // 7: paas.UpdateComposeSourceResponse + (*DestroyServiceRequest)(nil), // 8: paas.DestroyServiceRequest + (*DestroyServiceResponse)(nil), // 9: paas.DestroyServiceResponse + (*GetServiceStatusRequest)(nil), // 10: paas.GetServiceStatusRequest + (*GetServiceStatusResponse)(nil), // 11: paas.GetServiceStatusResponse + (*ContainerStatus)(nil), // 12: paas.ContainerStatus + (*ListServicesRequest)(nil), // 13: paas.ListServicesRequest + (*ListServicesResponse)(nil), // 14: paas.ListServicesResponse + (*ServiceInfo)(nil), // 15: paas.ServiceInfo + (*AddDomainRequest)(nil), // 16: paas.AddDomainRequest + (*AddDomainResponse)(nil), // 17: paas.AddDomainResponse + (*RemoveDomainRequest)(nil), // 18: paas.RemoveDomainRequest + (*RemoveDomainResponse)(nil), // 19: paas.RemoveDomainResponse + (*ListDomainsRequest)(nil), // 20: paas.ListDomainsRequest + (*ListDomainsResponse)(nil), // 21: paas.ListDomainsResponse + (*DomainInfo)(nil), // 22: paas.DomainInfo + (*UpdateEnvRequest)(nil), // 23: paas.UpdateEnvRequest + (*UpdateEnvResponse)(nil), // 24: paas.UpdateEnvResponse + (*RestartServiceRequest)(nil), // 25: paas.RestartServiceRequest + (*RestartServiceResponse)(nil), // 26: paas.RestartServiceResponse + (*StopServiceRequest)(nil), // 27: paas.StopServiceRequest + (*StopServiceResponse)(nil), // 28: paas.StopServiceResponse + (*StartServiceRequest)(nil), // 29: paas.StartServiceRequest + (*StartServiceResponse)(nil), // 30: paas.StartServiceResponse + (*GetLogsRequest)(nil), // 31: paas.GetLogsRequest + (*GetLogsResponse)(nil), // 32: paas.GetLogsResponse + (*LogEntry)(nil), // 33: paas.LogEntry + (*ScaleServiceRequest)(nil), // 34: paas.ScaleServiceRequest + (*ScaleServiceResponse)(nil), // 35: paas.ScaleServiceResponse + (*ResourceLimits)(nil), // 36: paas.ResourceLimits + (*UpdateResourcesRequest)(nil), // 37: paas.UpdateResourcesRequest + (*UpdateResourcesResponse)(nil), // 38: paas.UpdateResourcesResponse + (*UpdateDeployRequest)(nil), // 39: paas.UpdateDeployRequest + (*UpdateDeployResponse)(nil), // 40: paas.UpdateDeployResponse + nil, // 41: paas.DeployContainerRequest.EnvEntry + nil, // 42: paas.DeployComposeRequest.EnvEntry + nil, // 43: paas.UpdateEnvRequest.EnvEntry } var file_proto_paas_proto_depIdxs = []int32{ - 39, // 0: paas.DeployContainerRequest.env:type_name -> paas.DeployContainerRequest.EnvEntry - 34, // 1: paas.DeployContainerRequest.resources:type_name -> paas.ResourceLimits - 40, // 2: paas.DeployComposeRequest.env:type_name -> paas.DeployComposeRequest.EnvEntry + 41, // 0: paas.DeployContainerRequest.env:type_name -> paas.DeployContainerRequest.EnvEntry + 36, // 1: paas.DeployContainerRequest.resources:type_name -> paas.ResourceLimits + 42, // 2: paas.DeployComposeRequest.env:type_name -> paas.DeployComposeRequest.EnvEntry 3, // 3: paas.DeployComposeRequest.domains:type_name -> paas.ComposeDomain 5, // 4: paas.DeployComposeResponse.domains:type_name -> paas.DomainMapping - 10, // 5: paas.GetServiceStatusResponse.containers:type_name -> paas.ContainerStatus - 13, // 6: paas.ListServicesResponse.services:type_name -> paas.ServiceInfo - 20, // 7: paas.ListDomainsResponse.domains:type_name -> paas.DomainInfo - 41, // 8: paas.UpdateEnvRequest.env:type_name -> paas.UpdateEnvRequest.EnvEntry - 31, // 9: paas.GetLogsResponse.entries:type_name -> paas.LogEntry - 34, // 10: paas.UpdateResourcesRequest.resources:type_name -> paas.ResourceLimits + 12, // 5: paas.GetServiceStatusResponse.containers:type_name -> paas.ContainerStatus + 15, // 6: paas.ListServicesResponse.services:type_name -> paas.ServiceInfo + 22, // 7: paas.ListDomainsResponse.domains:type_name -> paas.DomainInfo + 43, // 8: paas.UpdateEnvRequest.env:type_name -> paas.UpdateEnvRequest.EnvEntry + 33, // 9: paas.GetLogsResponse.entries:type_name -> paas.LogEntry + 36, // 10: paas.UpdateResourcesRequest.resources:type_name -> paas.ResourceLimits 0, // 11: paas.PaaS.DeployContainer:input_type -> paas.DeployContainerRequest 2, // 12: paas.PaaS.DeployCompose:input_type -> paas.DeployComposeRequest - 6, // 13: paas.PaaS.DestroyService:input_type -> paas.DestroyServiceRequest - 8, // 14: paas.PaaS.GetServiceStatus:input_type -> paas.GetServiceStatusRequest - 11, // 15: paas.PaaS.ListServices:input_type -> paas.ListServicesRequest - 14, // 16: paas.PaaS.AddDomain:input_type -> paas.AddDomainRequest - 16, // 17: paas.PaaS.RemoveDomain:input_type -> paas.RemoveDomainRequest - 18, // 18: paas.PaaS.ListDomains:input_type -> paas.ListDomainsRequest - 21, // 19: paas.PaaS.UpdateEnv:input_type -> paas.UpdateEnvRequest - 23, // 20: paas.PaaS.RestartService:input_type -> paas.RestartServiceRequest - 25, // 21: paas.PaaS.StopService:input_type -> paas.StopServiceRequest - 27, // 22: paas.PaaS.StartService:input_type -> paas.StartServiceRequest - 29, // 23: paas.PaaS.GetLogs:input_type -> paas.GetLogsRequest - 32, // 24: paas.PaaS.ScaleService:input_type -> paas.ScaleServiceRequest - 35, // 25: paas.PaaS.UpdateResources:input_type -> paas.UpdateResourcesRequest - 37, // 26: paas.PaaS.UpdateDeploy:input_type -> paas.UpdateDeployRequest - 1, // 27: paas.PaaS.DeployContainer:output_type -> paas.DeployContainerResponse - 4, // 28: paas.PaaS.DeployCompose:output_type -> paas.DeployComposeResponse - 7, // 29: paas.PaaS.DestroyService:output_type -> paas.DestroyServiceResponse - 9, // 30: paas.PaaS.GetServiceStatus:output_type -> paas.GetServiceStatusResponse - 12, // 31: paas.PaaS.ListServices:output_type -> paas.ListServicesResponse - 15, // 32: paas.PaaS.AddDomain:output_type -> paas.AddDomainResponse - 17, // 33: paas.PaaS.RemoveDomain:output_type -> paas.RemoveDomainResponse - 19, // 34: paas.PaaS.ListDomains:output_type -> paas.ListDomainsResponse - 22, // 35: paas.PaaS.UpdateEnv:output_type -> paas.UpdateEnvResponse - 24, // 36: paas.PaaS.RestartService:output_type -> paas.RestartServiceResponse - 26, // 37: paas.PaaS.StopService:output_type -> paas.StopServiceResponse - 28, // 38: paas.PaaS.StartService:output_type -> paas.StartServiceResponse - 30, // 39: paas.PaaS.GetLogs:output_type -> paas.GetLogsResponse - 33, // 40: paas.PaaS.ScaleService:output_type -> paas.ScaleServiceResponse - 36, // 41: paas.PaaS.UpdateResources:output_type -> paas.UpdateResourcesResponse - 38, // 42: paas.PaaS.UpdateDeploy:output_type -> paas.UpdateDeployResponse - 27, // [27:43] is the sub-list for method output_type - 11, // [11:27] is the sub-list for method input_type + 6, // 13: paas.PaaS.UpdateComposeSource:input_type -> paas.UpdateComposeSourceRequest + 8, // 14: paas.PaaS.DestroyService:input_type -> paas.DestroyServiceRequest + 10, // 15: paas.PaaS.GetServiceStatus:input_type -> paas.GetServiceStatusRequest + 13, // 16: paas.PaaS.ListServices:input_type -> paas.ListServicesRequest + 16, // 17: paas.PaaS.AddDomain:input_type -> paas.AddDomainRequest + 18, // 18: paas.PaaS.RemoveDomain:input_type -> paas.RemoveDomainRequest + 20, // 19: paas.PaaS.ListDomains:input_type -> paas.ListDomainsRequest + 23, // 20: paas.PaaS.UpdateEnv:input_type -> paas.UpdateEnvRequest + 25, // 21: paas.PaaS.RestartService:input_type -> paas.RestartServiceRequest + 27, // 22: paas.PaaS.StopService:input_type -> paas.StopServiceRequest + 29, // 23: paas.PaaS.StartService:input_type -> paas.StartServiceRequest + 31, // 24: paas.PaaS.GetLogs:input_type -> paas.GetLogsRequest + 34, // 25: paas.PaaS.ScaleService:input_type -> paas.ScaleServiceRequest + 37, // 26: paas.PaaS.UpdateResources:input_type -> paas.UpdateResourcesRequest + 39, // 27: paas.PaaS.UpdateDeploy:input_type -> paas.UpdateDeployRequest + 1, // 28: paas.PaaS.DeployContainer:output_type -> paas.DeployContainerResponse + 4, // 29: paas.PaaS.DeployCompose:output_type -> paas.DeployComposeResponse + 7, // 30: paas.PaaS.UpdateComposeSource:output_type -> paas.UpdateComposeSourceResponse + 9, // 31: paas.PaaS.DestroyService:output_type -> paas.DestroyServiceResponse + 11, // 32: paas.PaaS.GetServiceStatus:output_type -> paas.GetServiceStatusResponse + 14, // 33: paas.PaaS.ListServices:output_type -> paas.ListServicesResponse + 17, // 34: paas.PaaS.AddDomain:output_type -> paas.AddDomainResponse + 19, // 35: paas.PaaS.RemoveDomain:output_type -> paas.RemoveDomainResponse + 21, // 36: paas.PaaS.ListDomains:output_type -> paas.ListDomainsResponse + 24, // 37: paas.PaaS.UpdateEnv:output_type -> paas.UpdateEnvResponse + 26, // 38: paas.PaaS.RestartService:output_type -> paas.RestartServiceResponse + 28, // 39: paas.PaaS.StopService:output_type -> paas.StopServiceResponse + 30, // 40: paas.PaaS.StartService:output_type -> paas.StartServiceResponse + 32, // 41: paas.PaaS.GetLogs:output_type -> paas.GetLogsResponse + 35, // 42: paas.PaaS.ScaleService:output_type -> paas.ScaleServiceResponse + 38, // 43: paas.PaaS.UpdateResources:output_type -> paas.UpdateResourcesResponse + 40, // 44: paas.PaaS.UpdateDeploy:output_type -> paas.UpdateDeployResponse + 28, // [28:45] is the sub-list for method output_type + 11, // [11:28] is the sub-list for method input_type 11, // [11:11] is the sub-list for extension type_name 11, // [11:11] is the sub-list for extension extendee 0, // [0:11] is the sub-list for field type_name @@ -2856,7 +2990,7 @@ func file_proto_paas_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_paas_proto_rawDesc), len(file_proto_paas_proto_rawDesc)), NumEnums: 0, - NumMessages: 42, + NumMessages: 44, NumExtensions: 0, NumServices: 1, }, diff --git a/proto/paas.proto b/proto/paas.proto index 5b34788..fa11984 100644 --- a/proto/paas.proto +++ b/proto/paas.proto @@ -12,6 +12,9 @@ service PaaS { // Deploy a full stack from docker-compose content rpc DeployCompose(DeployComposeRequest) returns (DeployComposeResponse); + // Replace the compose content of an existing compose service + rpc UpdateComposeSource(UpdateComposeSourceRequest) returns (UpdateComposeSourceResponse); + // Destroy a deployed service rpc DestroyService(DestroyServiceRequest) returns (DestroyServiceResponse); @@ -103,6 +106,20 @@ message DomainMapping { int32 port = 3; } +// --- Update Compose Source --- + +message UpdateComposeSourceRequest { + string project = 1; + string service = 2; + string compose_content = 3; // full replacement docker-compose.yml content + bool skip_deploy = 4; // store the new content without redeploying +} + +message UpdateComposeSourceResponse { + bool deployed = 1; // false when skip_deploy was set + string status = 2; +} + // --- Destroy --- message DestroyServiceRequest { diff --git a/proto/paas_grpc.pb.go b/proto/paas_grpc.pb.go index caf92cf..345177c 100644 --- a/proto/paas_grpc.pb.go +++ b/proto/paas_grpc.pb.go @@ -19,22 +19,23 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - PaaS_DeployContainer_FullMethodName = "/paas.PaaS/DeployContainer" - PaaS_DeployCompose_FullMethodName = "/paas.PaaS/DeployCompose" - PaaS_DestroyService_FullMethodName = "/paas.PaaS/DestroyService" - PaaS_GetServiceStatus_FullMethodName = "/paas.PaaS/GetServiceStatus" - PaaS_ListServices_FullMethodName = "/paas.PaaS/ListServices" - PaaS_AddDomain_FullMethodName = "/paas.PaaS/AddDomain" - PaaS_RemoveDomain_FullMethodName = "/paas.PaaS/RemoveDomain" - PaaS_ListDomains_FullMethodName = "/paas.PaaS/ListDomains" - PaaS_UpdateEnv_FullMethodName = "/paas.PaaS/UpdateEnv" - PaaS_RestartService_FullMethodName = "/paas.PaaS/RestartService" - PaaS_StopService_FullMethodName = "/paas.PaaS/StopService" - PaaS_StartService_FullMethodName = "/paas.PaaS/StartService" - PaaS_GetLogs_FullMethodName = "/paas.PaaS/GetLogs" - PaaS_ScaleService_FullMethodName = "/paas.PaaS/ScaleService" - PaaS_UpdateResources_FullMethodName = "/paas.PaaS/UpdateResources" - PaaS_UpdateDeploy_FullMethodName = "/paas.PaaS/UpdateDeploy" + PaaS_DeployContainer_FullMethodName = "/paas.PaaS/DeployContainer" + PaaS_DeployCompose_FullMethodName = "/paas.PaaS/DeployCompose" + PaaS_UpdateComposeSource_FullMethodName = "/paas.PaaS/UpdateComposeSource" + PaaS_DestroyService_FullMethodName = "/paas.PaaS/DestroyService" + PaaS_GetServiceStatus_FullMethodName = "/paas.PaaS/GetServiceStatus" + PaaS_ListServices_FullMethodName = "/paas.PaaS/ListServices" + PaaS_AddDomain_FullMethodName = "/paas.PaaS/AddDomain" + PaaS_RemoveDomain_FullMethodName = "/paas.PaaS/RemoveDomain" + PaaS_ListDomains_FullMethodName = "/paas.PaaS/ListDomains" + PaaS_UpdateEnv_FullMethodName = "/paas.PaaS/UpdateEnv" + PaaS_RestartService_FullMethodName = "/paas.PaaS/RestartService" + PaaS_StopService_FullMethodName = "/paas.PaaS/StopService" + PaaS_StartService_FullMethodName = "/paas.PaaS/StartService" + PaaS_GetLogs_FullMethodName = "/paas.PaaS/GetLogs" + PaaS_ScaleService_FullMethodName = "/paas.PaaS/ScaleService" + PaaS_UpdateResources_FullMethodName = "/paas.PaaS/UpdateResources" + PaaS_UpdateDeploy_FullMethodName = "/paas.PaaS/UpdateDeploy" ) // PaaSClient is the client API for PaaS service. @@ -47,6 +48,8 @@ type PaaSClient interface { DeployContainer(ctx context.Context, in *DeployContainerRequest, opts ...grpc.CallOption) (*DeployContainerResponse, error) // Deploy a full stack from docker-compose content DeployCompose(ctx context.Context, in *DeployComposeRequest, opts ...grpc.CallOption) (*DeployComposeResponse, error) + // Replace the compose content of an existing compose service + UpdateComposeSource(ctx context.Context, in *UpdateComposeSourceRequest, opts ...grpc.CallOption) (*UpdateComposeSourceResponse, error) // Destroy a deployed service DestroyService(ctx context.Context, in *DestroyServiceRequest, opts ...grpc.CallOption) (*DestroyServiceResponse, error) // Get service status and info @@ -105,6 +108,16 @@ func (c *paaSClient) DeployCompose(ctx context.Context, in *DeployComposeRequest return out, nil } +func (c *paaSClient) UpdateComposeSource(ctx context.Context, in *UpdateComposeSourceRequest, opts ...grpc.CallOption) (*UpdateComposeSourceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateComposeSourceResponse) + err := c.cc.Invoke(ctx, PaaS_UpdateComposeSource_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *paaSClient) DestroyService(ctx context.Context, in *DestroyServiceRequest, opts ...grpc.CallOption) (*DestroyServiceResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DestroyServiceResponse) @@ -255,6 +268,8 @@ type PaaSServer interface { DeployContainer(context.Context, *DeployContainerRequest) (*DeployContainerResponse, error) // Deploy a full stack from docker-compose content DeployCompose(context.Context, *DeployComposeRequest) (*DeployComposeResponse, error) + // Replace the compose content of an existing compose service + UpdateComposeSource(context.Context, *UpdateComposeSourceRequest) (*UpdateComposeSourceResponse, error) // Destroy a deployed service DestroyService(context.Context, *DestroyServiceRequest) (*DestroyServiceResponse, error) // Get service status and info @@ -299,6 +314,9 @@ func (UnimplementedPaaSServer) DeployContainer(context.Context, *DeployContainer func (UnimplementedPaaSServer) DeployCompose(context.Context, *DeployComposeRequest) (*DeployComposeResponse, error) { return nil, status.Error(codes.Unimplemented, "method DeployCompose not implemented") } +func (UnimplementedPaaSServer) UpdateComposeSource(context.Context, *UpdateComposeSourceRequest) (*UpdateComposeSourceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateComposeSource not implemented") +} func (UnimplementedPaaSServer) DestroyService(context.Context, *DestroyServiceRequest) (*DestroyServiceResponse, error) { return nil, status.Error(codes.Unimplemented, "method DestroyService not implemented") } @@ -398,6 +416,24 @@ func _PaaS_DeployCompose_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _PaaS_UpdateComposeSource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateComposeSourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PaaSServer).UpdateComposeSource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PaaS_UpdateComposeSource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PaaSServer).UpdateComposeSource(ctx, req.(*UpdateComposeSourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _PaaS_DestroyService_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DestroyServiceRequest) if err := dec(in); err != nil { @@ -665,6 +701,10 @@ var PaaS_ServiceDesc = grpc.ServiceDesc{ MethodName: "DeployCompose", Handler: _PaaS_DeployCompose_Handler, }, + { + MethodName: "UpdateComposeSource", + Handler: _PaaS_UpdateComposeSource_Handler, + }, { MethodName: "DestroyService", Handler: _PaaS_DestroyService_Handler, From 4e94c4e1e14b762e650a6af6769e58c5202f9ccc Mon Sep 17 00:00:00 2001 From: Indra Gunanda Date: Sat, 8 Aug 2026 20:36:32 +0700 Subject: [PATCH 2/3] docs: v0.2.0 release notes and UpdateComposeSource guide - CHANGELOG: v0.2.0 entry covering the new RPC, the dropped composeFile field and the set-source-inline argument change. - Deployment guide: new "Update an existing Compose stack" section under the gRPC Compose chapter, with the exact DeployCompose error it works around and a field table. The CLI Compose chapter points at it. - README: UpdateComposeSource in the RPC table, a grpcurl example, RPC count 17, corrected set-source-inline usage. - Bump documented image tags to 0.2.0. --- CHANGELOG.md | 13 ++++++++++++- README.md | 8 ++++---- docs/deployment-guide.md | 18 +++++++++--------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b46c8ad..f90ac27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,9 @@ All notable changes to this project are documented here. Versions follow the `vMAJOR.MINOR.PATCH` git tags that drive the release pipeline. -## Unreleased +## v0.2.0 + +Supported Easypanel versions: **2.32.0 .. 2.33.1**, unchanged from v0.1.0. ### Features @@ -25,6 +27,15 @@ All notable changes to this project are documented here. Versions follow the (`[project] [service] [compose-content]`). The old 4-argument form is still accepted and warns that the `compose-file` argument is ignored. +### Internal + +- `easypanel.IsNotFound` classifies the panel's `NOT_FOUND`, which it returns both + for a missing service and for one whose type does not match the route namespace. +- Unit tests cover the new RPC's field payload, sanitizing, `skip_deploy`, + `NotFound` mapping, input validation and deploy failures. New live test + `TestGRPC_UpdateComposeSourceE2E` also asserts that `DeployCompose` still + refuses an existing service, which is the gap this RPC fills. + ## v0.1.0 Supported Easypanel versions: **2.32.0 .. 2.33.1**, both bounds backed by pinned diff --git a/README.md b/README.md index c1b211a..d7b9d99 100644 --- a/README.md +++ b/README.md @@ -534,11 +534,11 @@ One `Dockerfile`, two final stages, both distroless + static (`CGO_ENABLED=0`), ```bash # Run the gRPC server docker run --rm -p 50051:50051 --env-file .env \ - ghcr.io/igun997/deploy-everything:0.1.0 + ghcr.io/igun997/deploy-everything:0.2.0 # Run a CLI command docker run --rm --env-file .env \ - ghcr.io/igun997/deploy-everything-cli:0.1.0 projects list + ghcr.io/igun997/deploy-everything-cli:0.2.0 projects list # Bleeding edge (current master) docker run --rm --env-file .env \ @@ -559,13 +559,13 @@ docker run --rm --env-file .env \ | Git event | Image tags | |-----------|-----------| | push/merge to `master` | `dev`, `dev-` | -| tag `v0.1.0` | `0.1.0`, `0.1`, `0`, `latest` | +| tag `v0.2.0` | `0.2.0`, `0.2`, `0`, `latest` | | tag `v1.2.3` | `1.2.3`, `1.2`, `1`, `latest` | | pre-release tag `v1.2.3-rc1` | `1.2.3-rc1` only (no `latest`, no `1.2`/`1`) | So `:dev` always points at the current `master`, `:latest` always points at the newest stable release. -The leading `v` is stripped from image tags: git tag `v0.1.0` produces image tag `0.1.0`. The binaries still report the full `v0.1.0` from `version`. +The leading `v` is stripped from image tags: git tag `v0.2.0` produces image tag `0.2.0`. The binaries still report the full `v0.2.0` from `version`. ### Cutting a release diff --git a/docs/deployment-guide.md b/docs/deployment-guide.md index 3e76980..792e339 100644 --- a/docs/deployment-guide.md +++ b/docs/deployment-guide.md @@ -5,7 +5,7 @@ This guide covers deploying the `deploy-everything` gRPC server and using both i - **gRPC API** — deploy prebuilt container images or complete Compose stacks. - **CLI** — deploy images, inline Dockerfiles, Git/GitHub sources, and Compose sources. -Examples use release `0.1.0`. +Examples use release `0.2.0`. Supported Easypanel versions: `2.32.0` .. `2.33.1`. Check what you are pointed at with `deploy-everything settings panel-version`. @@ -61,21 +61,21 @@ The gRPC deployment RPCs create services inside projects; they do not create pro ### Docker ```bash -docker pull ghcr.io/igun997/deploy-everything:0.1.0 +docker pull ghcr.io/igun997/deploy-everything:0.2.0 docker run -d \ --name deploy-everything \ --restart unless-stopped \ -p 50051:50051 \ --env-file .env \ - ghcr.io/igun997/deploy-everything:0.1.0 + ghcr.io/igun997/deploy-everything:0.2.0 ``` Verify: ```bash docker logs deploy-everything -docker run --rm ghcr.io/igun997/deploy-everything:0.1.0 version +docker run --rm ghcr.io/igun997/deploy-everything:0.2.0 version ``` ### Docker Compose @@ -84,7 +84,7 @@ docker run --rm ghcr.io/igun997/deploy-everything:0.1.0 version # compose.grpc-server.yml services: deploy-everything: - image: ghcr.io/igun997/deploy-everything:0.1.0 + image: ghcr.io/igun997/deploy-everything:0.2.0 restart: unless-stopped ports: - "50051:50051" @@ -612,14 +612,14 @@ Use the release binary: ```bash # Download archive from: -# https://github.com/igun997/deploy-everything/releases/tag/v0.1.0 +# https://github.com/igun997/deploy-everything/releases/tag/v0.2.0 ``` Or run the CLI image: ```bash docker run --rm --env-file .env \ - ghcr.io/igun997/deploy-everything-cli:0.1.0 version + ghcr.io/igun997/deploy-everything-cli:0.2.0 version ``` For local commands: @@ -634,7 +634,7 @@ Examples below use `$CLI`. If using Docker for every command, replace `$CLI` wit ```bash docker run --rm --env-file .env \ - ghcr.io/igun997/deploy-everything-cli:0.1.0 + ghcr.io/igun997/deploy-everything-cli:0.2.0 ``` ## 9. CLI: normal app deployment cases @@ -929,7 +929,7 @@ Send exact token configured in `GRPC_AUTH_TOKENS`: ```bash ./deploy-everything version ./grpc-server version -docker run --rm ghcr.io/igun997/deploy-everything-cli:0.1.0 version +docker run --rm ghcr.io/igun997/deploy-everything-cli:0.2.0 version # Easypanel version + supported range ./deploy-everything settings panel-version From e0f140f2e8e5c90c8bfc5fbe06b88da4667c8a3b Mon Sep 17 00:00:00 2001 From: Indra Gunanda Date: Sat, 8 Aug 2026 20:42:32 +0700 Subject: [PATCH 3/3] test: verify skipped compose deploy persists source --- grpc_e2e_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/grpc_e2e_test.go b/grpc_e2e_test.go index 010b5d3..8ddc1e2 100644 --- a/grpc_e2e_test.go +++ b/grpc_e2e_test.go @@ -648,6 +648,10 @@ func TestGRPC_UpdateComposeSourceE2E(t *testing.T) { if resp.Deployed || resp.Status != "stored" { t.Errorf("want deployed=false status=stored, got %v/%q", resp.Deployed, resp.Status) } + stored = composeSource(t, project, service) + if !strings.Contains(stored, "nginx:alpine") || strings.Contains(stored, "httpd:alpine") { + t.Errorf("skipDeploy did not persist the replacement source: %q", stored) + } t.Log(" ✓ stored without deploy") }