From 2ba413f0e7916253df7e796d886fc958f6305f87 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:36:40 +0000 Subject: [PATCH 1/2] Add PATCH /instances/{id} for egress proxy secret rotation Allow updating env values (credential secrets) on a running instance without restart. The egress proxy inject rules are atomically replaced so rotated keys take effect on the next outbound HTTPS request. Changes: - egressproxy.Service.UpdateInstanceInjectRules: swap inject rules for an already-registered instance without re-applying iptables - manager.UpdateInstance: merge env, validate credential bindings, refresh proxy rules, persist metadata - PATCH /instances/{id} API endpoint (OpenAPI + handler + codegen) - Integration test: create instance, verify initial key, rotate via UpdateInstance, verify rotated key is injected Co-Authored-By: Claude Opus 4.6 --- cmd/api/api/instances.go | 51 ++ lib/egressproxy/service.go | 21 + lib/instances/egress_proxy.go | 64 ++ .../egress_proxy_integration_test.go | 122 +++ lib/instances/manager.go | 10 + lib/instances/types.go | 6 + lib/oapi/oapi.go | 747 +++++++++++++----- openapi.yaml | 66 +- 8 files changed, 876 insertions(+), 211 deletions(-) diff --git a/cmd/api/api/instances.go b/cmd/api/api/instances.go index ba5b448a..ac41d6b5 100644 --- a/cmd/api/api/instances.go +++ b/cmd/api/api/instances.go @@ -425,6 +425,57 @@ func (s *ApiService) DeleteInstance(ctx context.Context, request oapi.DeleteInst return oapi.DeleteInstance204Response{}, nil } +// UpdateInstance updates a running instance (e.g. rotate credential secrets). +// The id parameter can be an instance ID, name, or ID prefix. +// Note: Resolution is handled by ResolveResource middleware. +func (s *ApiService) UpdateInstance(ctx context.Context, request oapi.UpdateInstanceRequestObject) (oapi.UpdateInstanceResponseObject, error) { + log := logger.FromContext(ctx) + + inst := mw.GetResolvedInstance[instances.Instance](ctx) + if inst == nil { + return oapi.UpdateInstance500JSONResponse{ + Code: "internal_error", + Message: "resource not resolved", + }, nil + } + + env := make(map[string]string) + if request.Body.Env != nil { + env = *request.Body.Env + } + + updated, err := s.InstanceManager.UpdateInstance(ctx, inst.Id, instances.UpdateInstanceRequest{ + Env: env, + }) + if err != nil { + switch { + case errors.Is(err, instances.ErrInvalidState): + return oapi.UpdateInstance409JSONResponse{ + Code: "invalid_state", + Message: err.Error(), + }, nil + case errors.Is(err, instances.ErrInvalidRequest): + return oapi.UpdateInstance400JSONResponse{ + Code: "invalid_request", + Message: err.Error(), + }, nil + case errors.Is(err, instances.ErrNotFound): + return oapi.UpdateInstance404JSONResponse{ + Code: "not_found", + Message: err.Error(), + }, nil + default: + log.ErrorContext(ctx, "failed to update instance", "error", err, "instance_id", inst.Id) + return oapi.UpdateInstance500JSONResponse{ + Code: "internal_error", + Message: "failed to update instance", + }, nil + } + } + + return oapi.UpdateInstance200JSONResponse(instanceToOAPI(*updated)), nil +} + // StandbyInstance puts an instance in standby (pause, snapshot, delete VMM) // The id parameter can be an instance ID, name, or ID prefix // Note: Resolution is handled by ResolveResource middleware diff --git a/lib/egressproxy/service.go b/lib/egressproxy/service.go index 75cd2e7f..fb6e48a6 100644 --- a/lib/egressproxy/service.go +++ b/lib/egressproxy/service.go @@ -217,6 +217,27 @@ func compileHeaderInjectRules(cfgRules []HeaderInjectRuleConfig) ([]headerInject return out, nil } +// UpdateInstanceInjectRules atomically replaces the header injection rules +// for an already-registered instance without re-applying iptables enforcement. +// Returns an error if the instance is not currently registered. +func (s *Service) UpdateInstanceInjectRules(instanceID string, rules []HeaderInjectRuleConfig) error { + compiled, err := compileHeaderInjectRules(rules) + if err != nil { + return err + } + + s.mu.Lock() + defer s.mu.Unlock() + + sourceIP, ok := s.sourceIPByInstance[instanceID] + if !ok { + return fmt.Errorf("instance %s is not registered with the egress proxy", instanceID) + } + + s.policiesBySourceIP[sourceIP] = sourcePolicy{headerInjectRules: compiled} + return nil +} + func (s *Service) UnregisterInstance(_ context.Context, instanceID string) { s.mu.Lock() sourceIP, ok := s.sourceIPByInstance[instanceID] diff --git a/lib/instances/egress_proxy.go b/lib/instances/egress_proxy.go index 8800d72e..00affdde 100644 --- a/lib/instances/egress_proxy.go +++ b/lib/instances/egress_proxy.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/kernel/hypeman/lib/egressproxy" + "github.com/kernel/hypeman/lib/logger" "github.com/kernel/hypeman/lib/network" ) @@ -237,6 +238,69 @@ func (m *manager) maybeRegisterEgressProxy(ctx context.Context, stored *StoredMe return &guestCfg, nil } +// updateInstance merges new env values into the stored metadata and refreshes +// the egress proxy inject rules so credential rotations take effect immediately +// on a running instance. +func (m *manager) updateInstance(ctx context.Context, id string, req UpdateInstanceRequest) (*Instance, error) { + log := logger.FromContext(ctx) + + meta, err := m.loadMetadata(id) + if err != nil { + return nil, ErrNotFound + } + + // Derive state to ensure instance is running + inst := m.toInstance(ctx, meta) + if inst.State != StateRunning && inst.State != StateInitializing { + return nil, fmt.Errorf("%w: instance must be running to update (current state: %s)", ErrInvalidState, inst.State) + } + + if len(req.Env) == 0 { + return &inst, nil + } + + // Merge new env values into existing env + if meta.Env == nil { + meta.Env = make(map[string]string) + } + for k, v := range req.Env { + meta.Env[k] = v + } + + // Validate that credential env bindings are still satisfied after the merge + if len(meta.Credentials) > 0 { + if err := validateCredentialEnvBindings(meta.Credentials, meta.Env); err != nil { + return nil, err + } + } + + // Update egress proxy inject rules if egress proxy is active + if meta.NetworkEgress != nil && meta.NetworkEgress.Enabled && len(meta.Credentials) > 0 { + m.egressProxyMu.Lock() + svc := m.egressProxy + m.egressProxyMu.Unlock() + + if svc != nil { + newRules := buildEgressProxyInjectRules(meta.NetworkEgress, meta.Credentials, meta.Env) + if err := svc.UpdateInstanceInjectRules(id, newRules); err != nil { + log.ErrorContext(ctx, "failed to update egress proxy inject rules", "instance_id", id, "error", err) + return nil, fmt.Errorf("update egress proxy inject rules: %w", err) + } + log.InfoContext(ctx, "updated egress proxy inject rules", "instance_id", id) + } + } + + // Persist updated metadata + if err := m.saveMetadata(meta); err != nil { + log.ErrorContext(ctx, "failed to save metadata after update", "instance_id", id, "error", err) + return nil, fmt.Errorf("save metadata: %w", err) + } + + updated := m.toInstance(ctx, meta) + log.InfoContext(ctx, "instance updated", "instance_id", id) + return &updated, nil +} + func (m *manager) unregisterEgressProxyInstance(ctx context.Context, instanceID string) { _ = ctx m.egressProxyMu.Lock() diff --git a/lib/instances/egress_proxy_integration_test.go b/lib/instances/egress_proxy_integration_test.go index 96fdd86c..81b94fc3 100644 --- a/lib/instances/egress_proxy_integration_test.go +++ b/lib/instances/egress_proxy_integration_test.go @@ -132,6 +132,128 @@ func TestEgressProxyRewritesHTTPSHeaders(t *testing.T) { deleted = true } +func TestEgressProxySecretUpdateOnRunningInstance(t *testing.T) { + requireKVMAccess(t) + + manager, _ := setupTestManager(t) + ctx := context.Background() + + caPEM, cert := mustGenerateTLSChain(t, []string{"localhost"}) + manager.egressProxyServiceOptions = egressproxy.ServiceOptions{ + AdditionalRootCAPEM: []string{caPEM}, + } + + target := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprint(w, r.Header.Get("Authorization")) + })) + target.TLS = &tls.Config{Certificates: []tls.Certificate{cert}} + target.StartTLS() + defer target.Close() + targetHostPort := strings.TrimPrefix(target.URL, "https://") + targetHost, targetPort, err := net.SplitHostPort(targetHostPort) + require.NoError(t, err) + + imageRef := integrationTestImageRef(t, "docker.io/library/nginx:alpine") + t.Logf("Pulling %s image...", imageRef) + created, err := manager.imageManager.CreateImage(ctx, images.CreateImageRequest{Name: imageRef}) + require.NoError(t, err) + + for i := 0; i < 120; i++ { + img, err := manager.imageManager.GetImage(ctx, created.Name) + if err == nil && img.Status == images.StatusReady { + break + } + time.Sleep(1 * time.Second) + } + img, err := manager.imageManager.GetImage(ctx, created.Name) + require.NoError(t, err) + require.Equal(t, images.StatusReady, img.Status) + + require.NoError(t, manager.systemManager.EnsureSystemFiles(ctx)) + require.NoError(t, manager.networkManager.Initialize(ctx, nil)) + + inst, err := manager.CreateInstance(ctx, CreateInstanceRequest{ + Name: "test-egress-update", + Image: imageRef, + Size: 2 * 1024 * 1024 * 1024, + HotplugSize: 512 * 1024 * 1024, + OverlaySize: 5 * 1024 * 1024 * 1024, + Vcpus: 1, + NetworkEnabled: true, + NetworkEgress: &NetworkEgressPolicy{ + Enabled: true, + }, + Credentials: map[string]CredentialPolicy{ + "OUTBOUND_OPENAI_KEY": { + Source: CredentialSource{Env: "OUTBOUND_OPENAI_KEY"}, + Inject: []CredentialInjectRule{ + { + Hosts: []string{"127.0.0.1"}, + As: CredentialInjectAs{ + Header: "Authorization", + Format: "Bearer ${value}", + }, + }, + }, + }, + }, + Env: map[string]string{ + "OUTBOUND_OPENAI_KEY": "original-key-111", + }, + Entrypoint: []string{"/bin/sh", "-lc"}, + Cmd: []string{"sleep 3600"}, + }) + require.NoError(t, err) + + deleted := false + t.Cleanup(func() { + if !deleted { + _ = manager.DeleteInstance(context.Background(), inst.Id) + } + }) + + require.NoError(t, waitForVMReady(ctx, inst.SocketPath, 10*time.Second)) + require.NoError(t, waitForLogMessage(ctx, manager, inst.Id, "[guest-agent] listening", 45*time.Second)) + + // Step 1: Verify initial credential is injected + curlCmd := fmt.Sprintf( + "NO_PROXY= no_proxy= curl -k -sS https://%s:%s", + targetHost, targetPort, + ) + output, exitCode, err := execCommand(ctx, inst, "sh", "-lc", curlCmd) + require.NoError(t, err) + require.Equal(t, 0, exitCode, "curl output: %s", output) + require.Contains(t, output, "Bearer original-key-111") + t.Log("Initial credential injection verified") + + // Step 2: Rotate the secret via UpdateInstance + updated, err := manager.UpdateInstance(ctx, inst.Id, UpdateInstanceRequest{ + Env: map[string]string{ + "OUTBOUND_OPENAI_KEY": "rotated-key-222", + }, + }) + require.NoError(t, err) + require.Equal(t, "rotated-key-222", updated.Env["OUTBOUND_OPENAI_KEY"]) + t.Log("Secret updated via UpdateInstance") + + // Step 3: Verify the rotated credential is now injected by the proxy + output, exitCode, err = execCommand(ctx, inst, "sh", "-lc", curlCmd) + require.NoError(t, err) + require.Equal(t, 0, exitCode, "curl output: %s", output) + require.Contains(t, output, "Bearer rotated-key-222") + t.Log("Rotated credential injection verified") + + // Step 4: Verify the guest still sees the mock value (not the real secret) + envOutput, envExitCode, err := execCommand(ctx, inst, "sh", "-lc", "printf '%s' \"$OUTBOUND_OPENAI_KEY\"") + require.NoError(t, err) + require.Equal(t, 0, envExitCode) + require.Equal(t, "mock-OUTBOUND_OPENAI_KEY", envOutput) + t.Log("Guest env still shows mock value after rotation") + + require.NoError(t, manager.DeleteInstance(ctx, inst.Id)) + deleted = true +} + func mustGenerateTLSChain(t *testing.T, dnsNames []string) (string, tls.Certificate) { t.Helper() diff --git a/lib/instances/manager.go b/lib/instances/manager.go index 8305a0c5..3c4fa86f 100644 --- a/lib/instances/manager.go +++ b/lib/instances/manager.go @@ -37,6 +37,7 @@ type Manager interface { StandbyInstance(ctx context.Context, id string) (*Instance, error) RestoreInstance(ctx context.Context, id string) (*Instance, error) RestoreSnapshot(ctx context.Context, id string, snapshotID string, req RestoreSnapshotRequest) (*Instance, error) + UpdateInstance(ctx context.Context, id string, req UpdateInstanceRequest) (*Instance, error) StopInstance(ctx context.Context, id string) (*Instance, error) StartInstance(ctx context.Context, id string, req StartInstanceRequest) (*Instance, error) StreamInstanceLogs(ctx context.Context, id string, tail int, follow bool, source LogSource) (<-chan string, error) @@ -297,6 +298,15 @@ func (m *manager) RestoreSnapshot(ctx context.Context, id string, snapshotID str return m.restoreSnapshot(ctx, id, snapshotID, req) } +// UpdateInstance updates a running instance's env values and refreshes egress +// proxy inject rules. Used for credential key rotation without instance restart. +func (m *manager) UpdateInstance(ctx context.Context, id string, req UpdateInstanceRequest) (*Instance, error) { + lock := m.getInstanceLock(id) + lock.Lock() + defer lock.Unlock() + return m.updateInstance(ctx, id, req) +} + // StopInstance gracefully stops a running instance func (m *manager) StopInstance(ctx context.Context, id string) (*Instance, error) { lock := m.getInstanceLock(id) diff --git a/lib/instances/types.go b/lib/instances/types.go index 8dc48837..91e5abf5 100644 --- a/lib/instances/types.go +++ b/lib/instances/types.go @@ -218,6 +218,12 @@ type CreateInstanceRequest struct { SkipGuestAgent bool // Skip guest-agent installation (disables exec/stat API) } +// UpdateInstanceRequest is the domain request for updating a running instance. +// Currently supports updating env values for credential key rotation. +type UpdateInstanceRequest struct { + Env map[string]string // Env values to merge into existing env (only credential-source keys are required) +} + // StartInstanceRequest is the domain request for starting a stopped instance type StartInstanceRequest struct { Entrypoint []string // Override entrypoint (nil = keep previous/image default) diff --git a/lib/oapi/oapi.go b/lib/oapi/oapi.go index 161067ad..3d5e46cc 100644 --- a/lib/oapi/oapi.go +++ b/lib/oapi/oapi.go @@ -1039,6 +1039,15 @@ type SnapshotTargetState string // Tags User-defined key-value tags. type Tags map[string]string +// UpdateInstanceRequest defines model for UpdateInstanceRequest. +type UpdateInstanceRequest struct { + // Env Environment variable values to merge into the existing instance env. + // Primarily used for credential key rotation: update the env vars + // referenced by credential policies so the egress proxy injects the + // new secret values on subsequent outbound requests. + Env *map[string]string `json:"env,omitempty"` +} + // Volume defines model for Volume. type Volume struct { // Attachments List of current attachments (empty if not attached) @@ -1264,6 +1273,9 @@ type CreateIngressJSONRequestBody = CreateIngressRequest // CreateInstanceJSONRequestBody defines body for CreateInstance for application/json ContentType. type CreateInstanceJSONRequestBody = CreateInstanceRequest +// UpdateInstanceJSONRequestBody defines body for UpdateInstance for application/json ContentType. +type UpdateInstanceJSONRequestBody = UpdateInstanceRequest + // ForkInstanceJSONRequestBody defines body for ForkInstance for application/json ContentType. type ForkInstanceJSONRequestBody = ForkInstanceRequest @@ -1435,6 +1447,11 @@ type ClientInterface interface { // GetInstance request GetInstance(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + // UpdateInstanceWithBody request with any body + UpdateInstanceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateInstance(ctx context.Context, id string, body UpdateInstanceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // ForkInstanceWithBody request with any body ForkInstanceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -1840,6 +1857,30 @@ func (c *Client) GetInstance(ctx context.Context, id string, reqEditors ...Reque return c.Client.Do(req) } +func (c *Client) UpdateInstanceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateInstanceRequestWithBody(c.Server, id, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateInstance(ctx context.Context, id string, body UpdateInstanceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateInstanceRequest(c.Server, id, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) ForkInstanceWithBody(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewForkInstanceRequestWithBody(c.Server, id, contentType, body) if err != nil { @@ -3088,6 +3129,53 @@ func NewGetInstanceRequest(server string, id string) (*http.Request, error) { return req, nil } +// NewUpdateInstanceRequest calls the generic UpdateInstance builder with application/json body +func NewUpdateInstanceRequest(server string, id string, body UpdateInstanceJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateInstanceRequestWithBody(server, id, "application/json", bodyReader) +} + +// NewUpdateInstanceRequestWithBody generates requests for UpdateInstance with any type of body +func NewUpdateInstanceRequestWithBody(server string, id string, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/instances/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PATCH", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewForkInstanceRequest calls the generic ForkInstance builder with application/json body func NewForkInstanceRequest(server string, id string, body ForkInstanceJSONRequestBody) (*http.Request, error) { var bodyReader io.Reader @@ -4277,6 +4365,11 @@ type ClientWithResponsesInterface interface { // GetInstanceWithResponse request GetInstanceWithResponse(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*GetInstanceResponse, error) + // UpdateInstanceWithBodyWithResponse request with any body + UpdateInstanceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateInstanceResponse, error) + + UpdateInstanceWithResponse(ctx context.Context, id string, body UpdateInstanceJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateInstanceResponse, error) + // ForkInstanceWithBodyWithResponse request with any body ForkInstanceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkInstanceResponse, error) @@ -4916,6 +5009,32 @@ func (r GetInstanceResponse) StatusCode() int { return 0 } +type UpdateInstanceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Instance + JSON400 *Error + JSON404 *Error + JSON409 *Error + JSON500 *Error +} + +// Status returns HTTPResponse.Status +func (r UpdateInstanceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateInstanceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type ForkInstanceResponse struct { Body []byte HTTPResponse *http.Response @@ -5701,6 +5820,23 @@ func (c *ClientWithResponses) GetInstanceWithResponse(ctx context.Context, id st return ParseGetInstanceResponse(rsp) } +// UpdateInstanceWithBodyWithResponse request with arbitrary body returning *UpdateInstanceResponse +func (c *ClientWithResponses) UpdateInstanceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateInstanceResponse, error) { + rsp, err := c.UpdateInstanceWithBody(ctx, id, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateInstanceResponse(rsp) +} + +func (c *ClientWithResponses) UpdateInstanceWithResponse(ctx context.Context, id string, body UpdateInstanceJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateInstanceResponse, error) { + rsp, err := c.UpdateInstance(ctx, id, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateInstanceResponse(rsp) +} + // ForkInstanceWithBodyWithResponse request with arbitrary body returning *ForkInstanceResponse func (c *ClientWithResponses) ForkInstanceWithBodyWithResponse(ctx context.Context, id string, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*ForkInstanceResponse, error) { rsp, err := c.ForkInstanceWithBody(ctx, id, contentType, body, reqEditors...) @@ -6917,6 +7053,60 @@ func ParseGetInstanceResponse(rsp *http.Response) (*GetInstanceResponse, error) return response, nil } +// ParseUpdateInstanceResponse parses an HTTP response from a UpdateInstanceWithResponse call +func ParseUpdateInstanceResponse(rsp *http.Response) (*UpdateInstanceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateInstanceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Instance + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 409: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON409 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + + } + + return response, nil +} + // ParseForkInstanceResponse parses an HTTP response from a ForkInstanceWithResponse call func ParseForkInstanceResponse(rsp *http.Response) (*ForkInstanceResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) @@ -7994,6 +8184,9 @@ type ServerInterface interface { // Get instance details // (GET /instances/{id}) GetInstance(w http.ResponseWriter, r *http.Request, id string) + // Update instance settings (e.g. rotate credential secrets) + // (PATCH /instances/{id}) + UpdateInstance(w http.ResponseWriter, r *http.Request, id string) // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) ForkInstance(w http.ResponseWriter, r *http.Request, id string) @@ -8204,6 +8397,12 @@ func (_ Unimplemented) GetInstance(w http.ResponseWriter, r *http.Request, id st w.WriteHeader(http.StatusNotImplemented) } +// Update instance settings (e.g. rotate credential secrets) +// (PATCH /instances/{id}) +func (_ Unimplemented) UpdateInstance(w http.ResponseWriter, r *http.Request, id string) { + w.WriteHeader(http.StatusNotImplemented) +} + // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) func (_ Unimplemented) ForkInstance(w http.ResponseWriter, r *http.Request, id string) { @@ -9004,6 +9203,37 @@ func (siw *ServerInterfaceWrapper) GetInstance(w http.ResponseWriter, r *http.Re handler.ServeHTTP(w, r) } +// UpdateInstance operation middleware +func (siw *ServerInterfaceWrapper) UpdateInstance(w http.ResponseWriter, r *http.Request) { + + var err error + + // ------------- Path parameter "id" ------------- + var id string + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + + ctx := r.Context() + + ctx = context.WithValue(ctx, BearerAuthScopes, []string{}) + + r = r.WithContext(ctx) + + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + siw.Handler.UpdateInstance(w, r, id) + })) + + for _, middleware := range siw.HandlerMiddlewares { + handler = middleware(handler) + } + + handler.ServeHTTP(w, r) +} + // ForkInstance operation middleware func (siw *ServerInterfaceWrapper) ForkInstance(w http.ResponseWriter, r *http.Request) { @@ -9994,6 +10224,9 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Group(func(r chi.Router) { r.Get(options.BaseURL+"/instances/{id}", wrapper.GetInstance) }) + r.Group(func(r chi.Router) { + r.Patch(options.BaseURL+"/instances/{id}", wrapper.UpdateInstance) + }) r.Group(func(r chi.Router) { r.Post(options.BaseURL+"/instances/{id}/fork", wrapper.ForkInstance) }) @@ -10981,6 +11214,60 @@ func (response GetInstance500JSONResponse) VisitGetInstanceResponse(w http.Respo return json.NewEncoder(w).Encode(response) } +type UpdateInstanceRequestObject struct { + Id string `json:"id"` + Body *UpdateInstanceJSONRequestBody +} + +type UpdateInstanceResponseObject interface { + VisitUpdateInstanceResponse(w http.ResponseWriter) error +} + +type UpdateInstance200JSONResponse Instance + +func (response UpdateInstance200JSONResponse) VisitUpdateInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type UpdateInstance400JSONResponse Error + +func (response UpdateInstance400JSONResponse) VisitUpdateInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type UpdateInstance404JSONResponse Error + +func (response UpdateInstance404JSONResponse) VisitUpdateInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(404) + + return json.NewEncoder(w).Encode(response) +} + +type UpdateInstance409JSONResponse Error + +func (response UpdateInstance409JSONResponse) VisitUpdateInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(409) + + return json.NewEncoder(w).Encode(response) +} + +type UpdateInstance500JSONResponse Error + +func (response UpdateInstance500JSONResponse) VisitUpdateInstanceResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type ForkInstanceRequestObject struct { Id string `json:"id"` Body *ForkInstanceJSONRequestBody @@ -12030,6 +12317,9 @@ type StrictServerInterface interface { // Get instance details // (GET /instances/{id}) GetInstance(ctx context.Context, request GetInstanceRequestObject) (GetInstanceResponseObject, error) + // Update instance settings (e.g. rotate credential secrets) + // (PATCH /instances/{id}) + UpdateInstance(ctx context.Context, request UpdateInstanceRequestObject) (UpdateInstanceResponseObject, error) // Fork an instance from stopped, standby, or running (with from_running=true) // (POST /instances/{id}/fork) ForkInstance(ctx context.Context, request ForkInstanceRequestObject) (ForkInstanceResponseObject, error) @@ -12747,6 +13037,39 @@ func (sh *strictHandler) GetInstance(w http.ResponseWriter, r *http.Request, id } } +// UpdateInstance operation middleware +func (sh *strictHandler) UpdateInstance(w http.ResponseWriter, r *http.Request, id string) { + var request UpdateInstanceRequestObject + + request.Id = id + + var body UpdateInstanceJSONRequestBody + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) + return + } + request.Body = &body + + handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { + return sh.ssi.UpdateInstance(ctx, request.(UpdateInstanceRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "UpdateInstance") + } + + response, err := handler(r.Context(), w, r, request) + + if err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } else if validResponse, ok := response.(UpdateInstanceResponseObject); ok { + if err := validResponse.VisitUpdateInstanceResponse(w); err != nil { + sh.options.ResponseErrorHandlerFunc(w, r, err) + } + } else if response != nil { + sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response)) + } +} + // ForkInstance operation middleware func (sh *strictHandler) ForkInstance(w http.ResponseWriter, r *http.Request, id string) { var request ForkInstanceRequestObject @@ -13375,216 +13698,220 @@ func (sh *strictHandler) GetVolume(w http.ResponseWriter, r *http.Request, id st var swaggerSpec = []string{ "H4sIAAAAAAAC/+x97XLbOpLoq6B0Z2vkGUmWP+I42jq117GTHO+JE9849t6do1wZIiEJYxLgAUA5Sip/", - "5wHmEedJbqEB8EugRDu2E28yNXUikyA+Gt2N7kZ/fG4FPE44I0zJ1uBzSwYzEmP4eaAUDmYXPEpj8o78", - "kRKp9ONE8IQIRQk0innK1CjBaqb/CokMBE0U5aw1aJ1iNUPXMyIImkMvSM54GoVoTBB8R8JWp0U+4jiJ", - "SGvQ2oyZ2gyxwq1OSy0S/UgqQdm09aXTEgSHnEULM8wEp5FqDSY4kqRTGfZEd42wRPqTLnyT9TfmPCKY", - "tb5Aj3+kVJCwNfi9uIwPWWM+/jsJlB78YI5phMcROSJzGpBlMASpEISpUSjonIhlUBya99ECjXnKQmTa", - "oTZLowjRCWKckY0SMNichlRDQjfRQ7cGSqTEA5kQ5jSioWcHDo+ReY2Oj1B7Rj6WB9l+Ot5v1XfJcEyW", - "O/01jTHrauDqabn+oW2x79e7vp4pj+N0NBU8TZZ7Pn57cnKO4CViaTwmotjj/nbWH2WKTInQHSYBHeEw", - "FERK//rdy+Lc+v1+f4C3B/1+r++b5ZywkItakJrXfpBu9UOyostGILX9L4H0zcXx0fEBOuQi4QLDt0sj", - "VRC7CJ7iuopoU94VH/4/T2kULmP9WD8mYkSZVJjV4OCxfanBxSdIzQiy36GLE9SecIFCMk6nU8qmG03w", - "XTOsiCgSjrBaHg6mimwbyhlSNCZS4ThpdVoTLmL9USvEinT1m0YDCoLXDKdbNBpsmdRSs5OjWNb17pog", - "ylBMo4hKEnAWyuIYlKm93frFFAiGCME9HOqFfoxiIiWeEtTWbFPzboakwiqViEo0wTQiYaM98iGCWczf", - "+RjRkDBFJ7RM3wadungcbG3veHlHjKdkFNKpPYnK3R/Bc41iuh+FoLV/IZrQFs3WAUMKMlke7yWwbhhE", - "kAkRROP4Vw6XCD4nTFOLHu9PMG7rf23mR/SmPZ83AZinefMvndYfKUnJKOGSmhkucS77RqMRgBrBF/45", - "w6tVe13AKKmwWE0f0OIOKNHMrxFszkzTL52WwtO1n7zXbaq8E1ijHbLEBWpZ5Is5YR4hKeBM2Rdl6Lzm", - "UxRRRpBtYfdC80Q9wC8RB5Z4R3DIwL9M/Hret2Be5kFNb/pdp0VYGmtgRnxahOaMYKHGpATMmiPMdpTP", - "rhb8pyXyqZxVWJLRag5yShkjIdItLWGbliiVIKkuLR+o6Iqq0ZwI6aU5mNZvVCHborariAdXExqR0QzL", - "mZkxDkOgVxydllbikdZK4i9ONBN0HYIUIZHi6OzXg+0ne8gO4IGh5KkIzAyWV1L4Wndv2iKFxRhHkRc3", - "6tHt5mf0Mob4MeAsI4y6syfDQIeYhtO17G7q7jutJJUz8wt4t54VnH2aDWj0ivTvD55FHwKTMFpCrc7k", - "lwHfJmaz0TTiGqYLlDL6R1oSsHvoWOsKCumDgoYk7CAMLzTLxqni3SlhRGg+hSaCxyBtFYRg1Ca9aa+D", - "hlou7GopuIu3u/1+tz9slcXYaLc7TVINCqwUEXqC/+933P100P1bv/vsQ/5z1Ot++OuffAjQVDJ3UqFd", - "Z9vRfge5yRbF9epE14nyt+b+xen7OI7Z6mPNJ26604fHy4KDWWvIgysiepRvRnQssFhssillHwcRVkSq", - "8spXt71TWMA6VgCBTTWYbgiGitIDaNyO+DURgebAEdGIJzuaCVMlOwhrvRmYF9Kn5L+jADNNC0a44AIR", - "FqJrqmYIQ7sytOJFFye0S81UW51WjD++JmyqZq3B3s4Snmskb9sf3Q9/cY82/sOL6iKNiAfJ3/FUUTZF", - "8Nqc6jMqUT4Hqki8dkccdNMIxLyYsmPz2VY2EywEXnz9DruFrNppo8zVbnUQeyT/t3MiBA3dqXp4coTa", - "Eb0iFt2RSBkapv3+TgAN4CexTwIex5iF5tlGD72NqdKnWZof0sYa1Ctu9+8tEsw4yBlRxPWCMlDXCDE5", - "DANBQD/B0cpjeBWIvcA6zPpdPrR/5VJ1Y8zwlIA2aRuiseBXRE8UJTyiASUSXZGFFlIWaKo77c6ppJp8", - "CJujOTZGg96QvZ9xSUwT90orIgGhc4JiHlyhJMIBmXFQxOc4SonsoOuZlhg0MxYER/YxEiTGlA3ZTE9S", - "BjwhodYhTDNYGrokbH6JYpwAlWJBgERRjBURFEf0EwkRN5/EJKT6gBoyAniNEqxJNgi40Kev3luCg1kB", - "Cn+W6NLIG5fQ/SVlGisvDV31hqy4859bb8/fP397/uZo9Pb0xZuD49FvL/5bPzYftQa/f24Z+2YmaDwn", - "WBCB/vQZ1vvFSKchEa1B6yBVMy7oJ2Ns+dJpaRhIjV84oT2eEIZpL+Bxq9P6S/HPD18+OHlKD0XYXJOB", - "Z2JfvLKMOQo9HOXIGfMksgYiEO0wmGqBw7w6Pd/Uh2uCpVQzwdPprEwY9mS/EUmEVF6NKB+NE9+cqLxC", - "x5tvkZY7UEQ1gWZyxla/f/J8Uw5b+o8n7o+NHjoyVAvT1yyECyv+yJlGHy2EA8ocnp4jHEU8sCaQidaV", - "JnSaChL2KpY36N3HnwlTYpFw6tPBKswpb7rMo7rd/O0NWNHmmLJNqbehG9wM7oA3t9YEXrA5FZzFWhub", - "Y0H1MSvLtPLm7dGL0Ys3F62B5uNhGlij4unbd+9bg9ZOv99v+RBUY9AaHvjq9PwQdsqQjUqidDqS9JNH", - "EjjI1odiEnNhNGD7DWrPyoKCoVsEmzNs7bx6bpBr6xXglduUkEpo7XoxHZcxZvvVcx+2zBYJEXMqfWay", - "X7N3bucLx7ph92XclkTMiciQFrC4V1A/goinYbcwZKc1oYIEAmu0a3Vaf5BYy+HzTxp18rl7vvNbrxrJ", - "n2sESxwllJEVkuV3IuFdc3EVcRx2t+5YwGNE6b6Xl/jGvCjvr8UJkqFEq7NkjWDhNQ3VbBTya6an7OGr", - "9g3KGmfM9aNeCY7+9Y9/XpzkatLWq3FiOe3W9pOv5LQV3qq79ppAsoWkiX8Z54l/ERcn//rHP91Kvu0i", - "jCByK6HO7v8L0wOwbI3rYema0lgzy2D5rxlRMyIKp7dDFv3I6MPwOXK4V1hKyTxavNNcYtR8TkSEFwXG", - "a+fU2uoD96vMSlAFtGq/02z0CumP17Bh3Zs75F9VdfTtvp/ReiblmdNzzSvsudBkJtlEtrZP7M/t5SnV", - "zOiKJiOQmkd4mplsV902n13RxIri8IXZxigyjCBMQXgfc656Q/ZfM8IQ7B1sMPlIAuB5UmGFDk6PJbqm", - "UQQGHmAqy0eLFuxztmKaS6X/K1LWQeNUaWmdK4Ks3gSDpDAXaDwmKGXYXWdXZGe7wCpeWbBcEcFINDKy", - "sWwIGfMRsh/VAgeWOsFSEWG4fZqU4XX028kZah8tGI5pgH4zvZ7wMI0IOksTzQ82ytDrDFkiyFyrEGwK", - "xkZqx+UTxFPV5ZOuEoS4KcbQWWYis3et81en5/a2Xm70huwd0YAlLCQhzNmdOBKpGVYo5OzPmmJJWO62", - "OH4F6H5avokq32nNgyQt78h2dTfewH26XvucCpXiSLPKkjTovV43jhseqd/4hRS1D8u2MuTEqnwv2tTe", - "YXoGL45lmdhvtjCCTmOzRUETXzJgODXxc7PJrun/mLmJrDTb5JriV4x1Zjqpgsj23XEruwWUjjOYlGGF", - "7wY8B7KgWdeaxUMiFWUGnXRbZAU6idqXWhm3eKzV78sOuvxL6YEmXacZaPHgGhloADtg+lGx/6pNYa22", - "31ynq2wOlrffjwNZ62eE5ltICcykPhq1iJSQHvoVeDBSJE40I2JTRCWShneSEDF+/e+IG5nEfTpkemrS", - "eGlYcGQ2H0mnjLLphpbS9bmCw9AYhiapSoVuN6cyh2YZdZzxpbqA92Z2xLDTOJX6QA2iNCTo0hloLsti", - "3bL5Zlmjs/acJQXFgAQUE9DV1GacKj28XnCMVTDTcOKpMm5bdumyPIGykWjddaadS3bRdYv9P8vYRRmo", - "1lxQYfx6cfaKBax6BfNinRXPyhl+C+MVWcCWO2siXrInFg2JfnOfIJJHc2JPzaIpcoyDK3OUGM8Ja4U0", - "9kRrQtTkXyFRr3Ft3VZoeDUGf1nSX0YlsODaxeYYY4V3Y75dZFxIL86M19F6rSQAfNAcBgikqcuOUXUI", - "GBAQ08gSoZAKEqil7imbDhl4cFzaJz3b26Umci1i+IjQp6t4RbmCsmK+KW0tKuysk9qgG700HlOlSNgp", - "ywZXhCRy/aK0dGztzh7juCDXgjpGZu09YUPpirAJFwGJrYz/dXrfi0JnXi3sZl0sO1QY+BbmbPEJ4SSJ", - "KAmN947ZD7CSSrtPYCKteuyGFaXLXOCXh7zEUXSJ2rbRBhJEr0W6vWKc5cj+/vDUoUB26Xxx0tEYqbnA", - "5UypZKT/I0eaii+rndlvHYXr7vSZJNF+H9Sj3d0du6vWZmYmXOm2bB7zOiXUb80Zw4mccVV7r3VFWbgO", - "UVwnv+m2tUaxTKCRtvl928USQbppMhUYHFPv0ip269tGgGY9513jc+5zLsygGqRS8bjgYojaFccIWnah", - "KANrzqNuiBUGC2JDM6eZ7rK7brwwXRkdqs4AMpqOPd429JPmlmhKp3i8UGWz/Vbfp6l97dWvm4tvW+rc", - "3o3mR8KR4qsdf+kEubZN/PzgHBgpPppPqKfn7DjKvUaoREHFx97qo7qLbhJQq8WDbBLMjF+mAQIIexcn", - "xSuz3pB14dgcoKNsgKzbrEsMMiEOzYVFm4vCJCg4e6HxYgNhdHHSQ++z2f5ZIq1ozImLA5hhicaEMJSC", - "xRdOsa45Q4sTSCUcdqr6uTVZmJCBDbgZ5PZdD/26SEiMrflHk0KMFQ3AwWhMK+uBY8RslL2KxaxofGpk", - "LFrlLv2OTKlUouIsjdrvXh7u7Ow8q5oNt590+1vdrSfvt/qDvv7/35r7Vd99VISvr4Myb7EuW0Xuc3h+", - "fLRtbZTlcdSnXfxs/+NHrJ7t0Wv57FM8FtO/7+AHiZvws7Kj3NcMtVNJRNexSY1VPg+zgiNXjQfZrR3D", - "7snPK3dbXdXWQOK9bnkfASE+V2Pr6HrzkI0qw1zrrFxY3LIGvkhAX8yppCB5WZ/AgHq9H4+ovHouCL4K", - "+TXznNsxnhI5MueZ340glca3hXy0VgnBuZpIc11ZtlZu7T7d3d/Z293v9z1xEMsIzwM6CvQJ1GgCbw+P", - "UYQXRCD4BrXhnilE44iPy4j+ZGdv/2n/2dZ203mYm5VmcMgUJvcValuI/NXF1Lk3pUltbz/d29nZ6e/t", - "be82mpW18zaalLMJl0SSpztPd7f2t3cbQcEniL9wcSlV3/nQ5zGg9R5zx9eVCQnohAYIIluQ/gC1YzjC", - "SHZJVKbJMQ5H1ujhPzsUppFc6ahgBrMtjYEsTiNFk4iYd7AhjWzIsPIj6MnnBEIZI2KUhe3coCcbzbP2", - "Yt6tJWuCSlFZJdCdUAlSSC48URKFA0Oha/kc7GY+sQ91eGDX0BAbXmvVqRuROYmKSGCOLj3ZmAuCMjwx", - "m1ZaFWVzHNFwRFmSelGiFpQvUwGyqOkU4TFPlbndgw0rDgK+wqB7TDS7bqafvuTiaq3XpT6JRyJlTHez", - "1ppzAAbwiTWxwCmOkf3aOfYXhL7sFs7cVdr3Er0zXxjLTv44SRWiTHGtnbJwvOjASNYCxJAgUnHgpNbQ", - "Z7tpKl365RYwcjqvCzNezjsfyOWkOzG39HerYYspUSOpsForsWhMeQ/tz6B5Yydu/eFaA0gDuDNy/RBA", - "By/3rkbbrmQ4uR+Ir/IBy2wNeSM4hQUNSQ8BdYEziouqq1DameJJQsLM/tMbsjNDKtkjaW4+9IcGDmpG", - "qEBc0CktD1w2jN2nM9lNUNFh063RsfjhsoQKL8Frop7o8UQRYSDoAoaLUT92E1qdloV9q9OynKgMGvfQ", - "A5Hcw3Fpiq9Oz2/qEpYIPqGRZ7nggmDfWs3MOUu93u2fdbf+j3F81PgGIhplxm0h5iHpVWLyoX2zk+fV", - "6flp3ZyyhAioOLulNWWOJh7OkfkjOIjYyyB7m2g1GIf++mDJBsll72c+WXYicEzG6WRCxCj2GNde6vfI", - "NDAeRZShk+dleVbLzU215tPS5oDaPMGBjWdvBn2PQa6yjE4Bmh/82/WOmGO4LgpOb5WwbWwgXA+9yVJQ", - "oFen5xLlzkEeS115e2vd1E9nC0kDHJkeTVArZUUDGyBnYwn5NP/QmiI9cnLslQ0dIaD2fJqkQIZn77rH", - "by8245DMO6U5gUPPjEdEz3ujwC3mLhYu96kvMYl5naXDIIZsSkAFWGUU3BhIBXr1QEdxhaORjLjPyeK9", - "fongJWpfvDSxSnoGHZSUtlI/L0ChhN97XorRHKlu2DMYsGoyLRG4V3csZ24x5pXC8kqD+kjlV4Ijk7Cm", - "jM95WLXbeH5V3mh+tZZ6bSe+cY+dP3aDmKnDkyMjMAScKUwZESgmCtv0OAXXFBCHWp1WV59RISYxeLhN", - "/n21V0qNCb4YBFVrxD1cynZxLwbcmijtd8Z1IEQxZnRCpLJR2qWR5QxvP9kbmFwSIZnsPtnr9Xo3DQ15", - "kceCNNqKTeM5X4gS6cnZ1+3DPUSANFnL59bpwftfW4PWZirFZsQDHG3KMWWDwt/Zn/kL+GH+HFPmjRxp", - "lH6ETpbSjpSvNPWZZZ4P9EqYdeXSuMRBgV97xVSjz4BHAoSreaN0FZ5q/cRg3NeG4946YUeeNUoVEnUU", - "HTkbJO2gn1ZbQp1gBG3smClTNMrzmSzbQG+VkUauDNpfCthPCMvC9KPI/Ao4m2uq8MXslxi4e/dV9wfW", - "O2UUUg8m/5fV9oxzAwQzrae31iZOkvVo6xcUM/7XNFeJjSj2nETfnOvf5o6tPPrb6X/+8X/l6dO/b/3x", - "+uLiv+ev/vPoDf3vi+j07VcFLq0OJv+mEeF3FgQOF0ulSPCmqHSCVeARqGZcqhoI2zdIceNn2UOHoPgN", - "hqyLXlNFBI4GaNiquPYOW6hNPuJAma8QZ0h3ZQMMNvTHp8b8oz/+7HTLL9U+QhtJIOyGZAFEMh2HPMaU", - "bQzZkNm+kFuIhDt9/StEAU5UKojePS3DRgs0FjjIIwjywTvoM06SLxtDBhou+aiEXkGChcqyX7gRACns", - "rIzPgG1OQhePbTTkIcvOpSwc29hoepkRBGzzVU9JP1C86gsX5QiY/b4vcB28tfRGRlQqAg7VGWZrNMrc", - "yNB+v8Qq9vv7/bUCfoZDK9APKGE5N6VDyga0ZBAYhjaMGzzLGtjSNW8yNIJ+ff/+VINB/3uGXEc5LLIt", - "Nkqe8d2TxkaoIlnw2tto+SNC9O42XJAxksFnUYNgnRfGrfP96zOkiIido3070OCc0ECvD67/qZSpRkWK", - "0cHhyYuNXoPkmgDbbP4r9vF9tsJqUIY1mtXZAjOM1/DtoOMjcKu1FJoLcOBW85ILFBkGk9P1AJ1LUvZR", - "ha0yt/pmJ6NFbnkzJ8CwteF6TKqcYoDeZXIjzqaSOUjmyOC6zOkSurUXL8bnZ6n3ij8teDNZvciyNvDw", - "wSpz7tYnbj0rWE3+HogDzVt/7IJN82a0XTSG6sH8qJHv/b1LKzs31VFvmhehHLpYCHvNUiM0z2lwH7kB", - "lvW1j1SNai/hkX5tr9ydVnJxgmZYsj8reFnRTbZ2njZKUqlHbXp9Xby45hMzpYyqXBxkdu1qIkKvaBQZ", - "bwZJpwxH6Blqnx2/+u349esN1EVv355Ut2LVF779aZAiwaH2q9NziFLBcuRugOqdHnHuOEw+Uqnkcpho", - "o4vU1SkZfi2lTfDG3W7cYS4Fd/u8tIyHyJLwLd36vr8MDStzKnxtYgQr7N5TXoRa5urLKVDms+bx3WY4", - "uJfplGJ2fPyhKBM4n+tbpxTotKjH3/RAahZIQnR8mmcWzI1SrvvKmp5t97b29ntb/X5vq9/ERBfjYMXY", - "JweHzQfvbxtDxACPB0E4IJOvMBFaxDbCG46u8UKioROvhy0jzxcE+QLZWhG80fXrcuaG2yVqqAoU61Ix", - "3CT1QrOcCivSA5+VEwM3ltGe/O2rcgiTpiezdV2wX41uYrwmKOBpFGo5aKwpz6hVJLTanyQqz7kMxHrO", - "rhi/ZuWlGxumpt8/UiIW6OLkpGTxFmRiU8o2WDi4PNTsA09utA3ba0TltbO5ZXqDh0hpUOWahdPqzhMY", - "FE1uzoXSYGgD01suPXqvvSkzW6PxZMWaKkaTkMxHaeoTivQrFzhxfn58VEIOjPe29vv7z7r746297m7Y", - "3+rirZ297vYT3J/sBE93apK6N3d7ub0nS5ma6wOVAPBggDRxaOFA01vmijJOFcrc1DQhH2rpEhXEWBOW", - "AzaBY0YVZD6kbKq7ARXdSrkmLtIkZ6SMKgjEhywulOklgy1Ed2KdjwboFbSFVziGcCE3Ca3blM0AOFwY", - "M6hmDG7oBP5aPeWzWaq02AXfyFmqkP4Llq3BYLWN1V0YHjNAbzh8I5yPKONVtcU0B9+r5eZVFadtvYKc", - "9ygMZhnmAL3MmGTGZi1bbUtifxrebR2bwWl7o+Q6Z3e8pbEl37mCV1inZSDa6rQcoMB7bNmPzM7LGyJR", - "REXf/QDBEbDQ3E8nVTSyuQVgJVQqGhilD8Pm1lGyTYNFwpE5wetu+4zzhz3ls48co7g4QW2IRvwrsjqh", - "/msjuxksUuXu9rPdZ3tPt5/tNYo5yCe4nsEfgmvS8uTWcvsgSUeuXkbN0g9Pz+Hs0+eqTGOj5Nu1F1w8", - "E8EDLWxShvICHPngz3rPiqEWIU/HUcFoZOOywJ+/SbWUmuutP2g0p5MJ++NTcLX9d0HjrY97cnvs1c2y", - "gfyC7HHR0Lmk9ZFx16Qu9HvDA0IJWRsw8o5IWAE6IwoB/nQRDuCQzjyKLMq5sBILcS9i7e7s7Ow/fbLd", - "CK/s7AqEMwL1c3mWJ3YGBRKDlqj97uwMbRYQzvTp3CwhLQOzApyfzpDNYtwveWBq1WfHhyU18lKONbbv", - "eVwL8gsrBNlFWaCDY1QmIC1RuRfaOzv9p7tP9p80I2OrcI3Ex9UcxqXCMOCx2UOKO98G4/j7g1OkexcT", - "HJQVjK3tnd0ne0/3bzQrdaNZQeYbk7HiBhPbf7r3ZHdne6tZ5JPPAG5j+koEW+ZdHqLzIIVnNzygWGa9", - "nbrTwid4LntjrnQAzT1Kq+6DN/EXzmO+qYReacFVFbW1XFaUcQtxyxtNzBx+FqnHqavCpSXQpq68qz13", - "T7GaHbMJ9yT0uYG+af2hnOU70XKQhJojIWGUhI53ZYqnFa3AwyqSBIUpsZAzopLAFuDY3PJA4h7mZDLK", - "pmXf8qUBm2iBZg6rI/xhXNuwicFK+v1y3osUYGVMzBLh3EOnkb2cypFfUVnuWJBpGmGBqu7qK6YsF3FE", - "2VWT3uUiHvOIBkh/ULUmTHgU8euRfiV/gbVsNFqd/mCUXzBXrANmcta9wGxIZdx8Cb/oVW5UnJvg5N80", - "329CmcUm9j/vrdNLrTsZj+5zRj8WEL0cAru73a/ze6vptOTxthwNcFPeblHWR/HOUf8gy1Trud0090cV", - "pbgsB5fW61stXFCu8vJblgRQ25kUXYhxGa6FUN9GB3GzO9Kq8dzNZlOSoDz67v6Tp3sNY62/StReUYju", - "KwTrebxCoK7ZqZMmUtv+k/1nz3Z2nzzbvpF85O5Zavan7q6luD+VhNQVme1JH/53o0mZmxb/lGpuW8oT", - "KiWXvvWEvqwg3TzGpkbrXlUENt9Jp+aXBfBmIu4KaemgJHIV6ie0yWRCAkXnZGTg1s0nU/HNajSHACc4", - "oGrh0QDxtcnTmTWpxIo06L0yWQ9Ibd823E9zLpmOc3eAthsc/cVodhVc2G+cskGm4zot8m11VKND2txs", - "FQtFAwNBng22eid/nQETXWNZulTQvwNItJfXx6jePpkWzQv5OVzPavnl9+q+eCd/3b7i9le2s6B1lITk", - "KsRXHaH1JKglgsZ5fD0nsq800Hqfjgp/sAfg7b4ajYvJVFZmqyllXslP3ZuP26yyx/J35gS7+XgFB4Kb", - "fFjNKwH4aOdgQZ733SmhRA02KS7WpwG8h+hwY9K+VXy4tYY/SIi4fXwvYeFL23FW8IJq7vPnvvKXYy7d", - "Y+51+zvd/t77rZ3Bk73B1tZ9BChkdxh1ptynn7aun0bbeLIb7S+e/rE1ezrdjne8Xh/3kH6yUkShko3S", - "riEhopoRpJpJR5KIMtKV2fXH+ovoFaFHxiiX4AUIeSs0spuoAa6w6QqqPSsvski8WOXAqSa0fwj/NDv7", - "lbpMdfrHR6unfav7hOpE/AhWnQrgU7PJQMDc1p1mGgW7KlCPF5A1i/KhTOk+voTEH1ZwsN8s4dZxKusW", - "bmeYZ6dwxOTuF0tYk79eApSPxa5OoFE5hMwdaTFfSeaSerfZM97bLa1zYC4kddl+slfO6nLQ/ZvJ4oJG", - "vcHmL3/9390Pf/mTP5NXSXWURHRDMgGJ+Yosuia1uEauXjn61RTskwrb/FeK4Bi4XXBFDHeN8cfifJ/0", - "M1v24g2Ol5YAqkZMWfb32gX5i+ouIZpxNqlLzRprCvEkzKKmUr4NvUeFxqhN4kQtXHips6lv3Mz55SDr", - "sKbC5p067vef3UWY4fnKuMIfMDFw0TfJTWitV9LS/tcG8/iNckdVH2Fj+bbJDss+rZUUblJ16212MU+Z", - "GoHpedm+pt8Zs7YNpJum1YwCmzFTmzZsdznak+AQcpCvvMjIqcw56nTho/X2+ZW3hoWVFWZSvzfGMW05", - "Wm4FgE41aK5nRJDCRsAHeezhDUFmjczrI9OMY46WRLvVbJgmYYugYLW2ADKA1SDILiKWbztW+9ae4I/Z", - "CCCBYrkkLcM6CmVOXz2HJEzvXFZEOnFdwDSqheier8eiJvUYljejiFXL6zbtvYRnedUK7ldHWxXkzMco", - "oeYyPmo2R4JUULU402zIRgBANZeD1KAh8CdYBDzOB4foTCieS+3NaeXmVmtjNEAHp8e2lguDsxxdnKCI", - "TkiwCCJig+uWHNpAn357eNw1UcFZjS89PFUAEJdV++D0GJL0CmnG7fe2e1AyFcoDJbQ1aO30tiBlsQYD", - "LHETkjnAT3vdpukQTr3j0J7Oz00T/ZXAMVFQce13z7WVIsIkh5DgsICnBcEmwVRYySaJ4DLNaGRUfwv+", - "xI7BD8wp0TEAx00dc6VaWNMiSd7abf2g0UEmnEmzodv9vsncyZQ9DnCevHXz79LcfeXjNpIyADwe59ol", - "kc9JOhbkXzqt3f7WjeazNt+qb9hzhm1RIALTfHJDINxq0GNm7jtczVliG+Z0BihUpLDfTaHqNI6xWDhw", - "5bBKuKwT0YhEGDI+mswkf+fjHrKqH4QDyhlPoxDqDSYmp71moxgpLHrTTwiLYEbnZMjs6WFy52IB4dIx", - "0qeGsVuVScMMbXbfsB0i1XMeLirQzbrb1N2BtFUGcDXsSJIRuF+P6tIOZXaThDIG6Uvzqs8u/8YSRzf5", - "pqFkuq/UFcNM5emLTaLpKwLuZRP60dthIz9JzfBgWwjUNcjC77c3/De0EE3md244yt4hC97yIad1BFug", - "K5ME3KUBFmMcRd6sS9OIj3Fk83FfEY/g9ApaWKAUA+/ckct4SEwQVbJQM87M73ScMpWa32PBryUR+mC2", - "wdQW1q4ckUFdKIxAYwhoNqla9JibZoqbn6/I4ktvyA7C2KXhsWU6cSS5TVSeFazKCs8OWW24X43d5NAW", - "LjFJgot5Vc00eaqSVPWQWQhRNgIcmkPaXTkj4ZApjj4LU2Vh8WXzcz7iF5CoCQ41nhSamCVtfqbhl7pZ", - "yxHWqx9BU49OQgAAw5Y+XYYt/XsqsJaoUwmV/YmEOpHT4pa2DWFzAdLKRhXCAWYo4Ulqa8ERZPOvl/qA", - "bBo4ipACUnLfahkIdrJmPfay3Zca0t60m6vRChlBksgCMfV39/30JEkgiE/t/s+zt28QHFV6D0yzPMIT", - "YGTKEmalUfXovSF7gYOZrRcGzv/DFg2HrUzmDTdgrqm0VwHdLghev+ip/WKG6dDwl15Pd2VkugH6/bPp", - "ZaBpKYlHil8RNmx96aDCiylVs3ScvfvgB2jdheVZiRGgtuH9Gy4XEhT4yo9Bc25gFiJueW20QBjlHKio", - "3Y8pw2JlIicP6C0EtYKJp7IIjM9DMPEMW4OhM/IMW51hi7A5PLOWoGHrix8CNvFYvae5yWVlm+VItNfv", - "b6z3JLLw9YjQpYaa/L4sSV/bdyZ4WKFrWfAwi3NhMnoHTVYyI249gOTzHGe1GX+KeGtEPKtPF4Q3+L54", - "Dhj0jYixHVckMK2AR04CW6mdGLSAODHQOJzfn1E4qJPgcuQtqh9VJXNZrdito7IAphg5/Nt9APyDcfPM", - "/jDus4caF0emBpXLc/240BE2yyFix68RvyLqe8C4/kOxUleA5Bvi72PBn1fEyn050CrcbBOKwBfNLdXY", - "Z0FwLG0vprHWVc9gTt0zwhR6AU979l+n8UCo6GXEp5cDZEAY8SmKKCPS+mRkdxj6ULSwhI9MqsbsO5vt", - "NJhhNiUStc35+a9//BMmRdn0X//4p5amzS8g903j7w+RkJczgoUaE6wuB+g3QpIujuicuMVArBKZE7FA", - "O31p68rqV57cqXLIhuwdUalgMvP01+sCmJgObSkPvR7KUiKRBBBCcbqJdUH/JS8666dlA8oHpejOks5l", - "V1BYgD4VHQ6ATyE14aBW/2r5rWdmzSX7WdWCu2TTX89fFPmoDPZ2zQRvyGAAxD66gxd20ah9dvZio4dA", - "xzBYAWEGIDHn3VjhufeTJ63nSYajlBkKQNnwpkLa/Fr775Ft08wAbHv8kSzAdXUA6k3AxuRBBAkdvH7q", - "Ck3MwX64OdOwzz575MoG1htob7/e4hDOT7ORInx3++xwbxnmtn5mDrJvoQKjti1nlqWwLBXp/FZI/yCn", - "RqG2a3Z0IG4SZz6YWnbI2SSigUJdNxfIkhGTTFUrI8hjYQfv7KwRduuqBvQWz7fNUnxK7UmXharkR979", - "nx6VQW9yjORBxzmu/TxJ1qHOEZUB198WsKUb4MQm8DTiS0anRSxaZ5A6gufZkbNSXDrKqj5bgnw405Qd", - "OmXVs+EBmOJRhSF+Q0ZYSUpYCNN/TNh8nu2iK5G8wnL1faFm/+GkoIe2YvnQ/DGZscIK2DQXnGWlqurQ", - "yxazuseNtiN4Fn5GhKNqM1GT4C5flvkUBTMSXJkF2UreqySCY1fsu4nqa/r7kTRfU0XsBhKLBflPEaWB", - "spvDapWCe2wzNd6ffgsj3Ei9vbt7XotgHiCDs8nYWaxNEkQsFyzY+KGueh/kNKtWC39ElHSaRpG78ZgT", - "ofJaasUzYPMzuCWtl+0dta08Ds7fve4SFnDwQ8t8qPxClCtxdLcSvtkws5SfaNJEJwRQOcSoF6C/Yv+N", - "uyDK8uX/2/ZLmzH/37Zfmpz5/7ZzYLLmb9wbsvQfijU/tMT9iJFPC9y0DDRgTaYQ0ToJNWvVUEh17X8o", - "OdUWtbuJpJrB9aew2kRYLYJrpbya1Re8R4nVlmL7NlcyGbL5oA2vnH/iDyapPqyVz2JkoWp/6drDppzk", - "Ii9/Zmt+Pz4HSpphXPHYaGiuzgly5fHhUPf4qGMr25l6dFmAyAMZr908Hly4teM+vOX6IB7TacpTWYw9", - "gUKGRNpgpYiUGfBjE7vz47lW8P6OsbT/kEfHg8vVP/H+niT+6oYa5m1uoNbJ/K5VU5nftoeSgaYahYld", - "e+eqXNg0Khs1ToWuDkxTNC6VLFp2dvTNy6eLoHOtqOTqAgINYjBk/6H1j98VwfGHX1yQTNrvb+/Bc8Lm", - "H35xcTLsxKEKYUpQIhEWBB28OYJrvylEr0MytDwkrzoPk+LM1Ia2ZUv/xylI+c1ncw3JYeFPDamRhlQA", - "12oNKauicp8qkhnkm+lIDt98ALepNX5qSQ+hJcl0MqEBJUzlGYCXnMRsAvFHGFvG7P1QwbmjdNA21pLy", - "0karBdA87d2DO/Zkgz+8cuQy7D1OH3luomJCp47kh2G9PvK94UP/YZnzw+shjxnFjMBfBd0yI9qc2ATE", - "fgHhJRdXTTHPk4fzzhHw7qWT4gq/Q9lET48Uqhx+QxEFDm/jW6+Rpiy5PABBLiVX/ZYunQ4SVrk1QZGU", - "TfM6l1TNeGqyqozsQ5OVTVOFrSYDIk9ge/3W7EWP/gAC6BuuEI2TiMQEsrZ1DTZBcdE0SbjI6o9RWUhF", - "fDP2p8mm6GBrktvYKsAdZBM2g7HObVgb7PbL2+XlmhGfrg+qzQZ3EaSeqNohO5cmyculEYUvUcZkkeJI", - "kogECl3PaDCDCFv9DPo3Abg4SS6zlBobrlhqMbMIDN6WRFAcQZVHHpl6pZfzOL4cLGeAuzg5gY9McK3J", - "9XY5QC7rW3ZASN2qGDGrVxFhqdAbGwfc1pgkeBSZHb3Up1BhfRs2ljZPeTJkvrhaRq5th3SCLgshtpc1", - "MbaOob7mU/mt5KVOfaIqsxbFkQDAGdwkLGzVGXZo5I+u3er3fflTGkb6mmncc6Dv0mRe82mWJKuEyjhJ", - "mqKvnSZg8TyOV+AwaheSmUsV8lT9VaqQCAEfW+yuQ27UxoH5Q+ErjajMliJz6eAB/bzmS5O1xgsqzVQL", - "+aTNX/M4bnVadj6e6rlfHzFd7XDZzKZ3phAW/VPSvknAc5nZFyKeKyeHrVtRL3Lbchw/vL7nyl1/YzT8", - "BvaxfBaUOVEF9javI/64IidNpZaqLGaS5/toJCv1Uk8lZaPyWZ6m/3+gimrWWq3P88BKagZin2ZWKm/x", - "zbXTrNrGTw0101C5QGFqhqvUu/lh1c6MoaCUlTRPK57eVvfMksxlYIY6hGzlhUDO8zY/u5/HtxAXvhNO", - "2Kmt+lKXzihf9PfAcmtqojXiud9ITrLHakFA+IYs2FVne2gOnEFFq3sZl/su2LAhuIwbF3kOVN6nrvDi", - "T2ZcMgMaS+ltmbETPpdsgQX2TFk3iXAdX7Zyai0DtlWgfnh9LddVfnCNLeBCGNcxcEZ7TKGLhTvDgurZ", - "TnAqSScjmI67t744OdmoIxqhVpKM+D4utG8nOVTKcsahvy6yoKFLUn94cmRT2lOJRMp66G1MIXP8FSEJ", - "pKSkPJUIfAB7xXpjdVXQsoJihCmxSDhlau0s8qb3M5kvt0rS/cB8ygZv//BmJVto97ExKeAd+vS2C1it", - "VClTZs97TeeurSgzmfW18IHHPNW9L9VDQxMaEbmQisTmzm6SRkBEkN7DZn+13xnftQ6iSkL18A74+iRE", - "xFRKypkcsjGZaKkkIUKPDQUnaUQK1w++m60zhTOueWpY3/dxtQUl0uA2B6s6qJWro+EkcdXRfNcnWUG3", - "W0/pJdxVIbmIxzyiAYoou5KoHdErI4OjuUSR/rGx8rJrBN/ddW7b21OWhvQxm3Bv+j+Dsxky/wgc7rjC", - "1txl/qNja69IkVgc/4GN9rM1uZavCYIjKAKaudmiVNGIfjKsTndCpaKBqZmEM9hBuRczXm/ITogSug0W", - "BAU8ikignK1hMxE82Bym/f5OkFCIh9ghMDlgePWvYxjx8PQc2pmSNJ0h039Ax+8PThHVMJ1gqzIXJmoL", - "26Pjzbdrrv/PAEz/g/Uxs8BVZOHf8J83uzf3oaylIVlDojxZpQDx5Ic3GFgJ7qe14HFaC8CJPVtNeypw", - "AEKxnKUq5NfMbxkwFVLl5mfz43hdKITCwezClYr+PqRdWy123TBugY+CKO2aQmLSk34Te70t6PtI0zlp", - "wLklgBBTDOrwnwKmUPiPht13f1lXhON3eFNnIepS/343tPXQJ5+dg4vwK8LjsZC5wTS3EihZWbQ+ZeGM", - "a3WzIBWCMAWpYHLRMsAJDqhadBCOXDVVWx4psyHlheDHguArfdL2huxdFkhpyzNp7arjVCsUUnllerDa", - "Uw+9nRMh03E2OQSMyeh5AHxbUDXAUWAqkZLJhASKzokpESprtK9sKveZljcfxLPR7qUF3WNTOfw4AbuX", - "o4XVOkqecrXpG86yVs3SN2S9FrxhCp4iK32eR66hqYJ/E5OdZ/ArWusWb1/dzHvtN/1Rw7HLXlL+SdhX", - "X7nKHyUr3lnBOaVp0occwx9b/oXCzEukWnLwWh8I3tij6z49rNYFgmeDP3Qg+JnXyeeRpaPCJbetugjw", - "7w8R+g/rXfzQEeCPG7e0KCGXQFfPiRpEgn8XGHg/IeDf2Lv+FiHg35W/J4Twfju/++/K09N6LGaenj+D", - "vO/TwdNEekNAa52Dp+F61vK8UlG6sG2aqUm2xx9JgrfGyhvI7w7sP1O2NVAZCsByp3CF3QDvlxbhSZyo", - "hbNG8Qn43eQ5BSX9BN57vsC5zOh8f/Fqt7DH3h16ODyttcb+TPX2YAbfPB/28dHjz+9WpLnSwbKpT50u", - "FsGMzkvxWqso2IIoEaSb8ATsrKEBmIWHO8sUFr3pJ2S77w3Z+xlxfyHqsmWQEIVUkEBFC0SZ4sARzBh/", - "lkhwrQnAey4WPvNtkXJfCh4f2NWsOQ8tTVljWO7mFy+6IVa4O3fcZoUJ7SuurE7wRxqnMTA8RBl69Ry1", - "yUclTPIGNNGaD6KTDKTkY0BIKAEnN4oT3urXWDbpJzKajpvMckUajrc2zQkKUql47Pb++Ai1cap4d0qY", - "3gst6k9Akk0En9PQ5MjNgTrnkYHqVg1Ab2p31UKF9QfPlQszuW8iwzQ5kKafaFJmC8btsTVojSnDMLm1", - "CS/KNGU8cPV4mIIfXE47DnNaP4+wapVtjYlayXFAVJyjSEv0Gz+Pucd8zBU9GdyZVjrtmmUxbebc0NDn", - "4D4ymGaOLw9rtr74fu7jC1WJH6HpfJ4ppHVm8+8LBfsPdz48tLn84hH7b70iTvkumMqhA92jD2Fe8wBH", - "KCRzEvEk1mKladvqtFIRtQatmVLJYHMz0u1mXKrBfn+/3/ry4cv/DwAA//+gy9hgdyoBAA==", + "5wHmEedJbqEB8EugRDu2E28yNXUikyDQaDQa3Y3++NwKeJxwRpiSrcHnlgxmJMbw80ApHMwueJTG5B35", + "IyVS6ceJ4AkRihJoFPOUqVGC1Uz/FRIZCJooyllr0DrFaoauZ0QQNIdekJzxNArRmCD4joStTot8xHES", + "kdagtRkztRlihVudllok+pFUgrJp60unJQgOOYsWZpgJTiPVGkxwJEmnMuyJ7hphifQnXfgm62/MeUQw", + "a32BHv9IqSBha/B7cRofssZ8/HcSKD34wRzTCI8jckTmNCDLaAhSIQhTo1DQORHLqDg076MFGvOUhci0", + "Q22WRhGiE8Q4IxslZLA5DanGhG6ih24NlEiJBzMhwDSioWcFDo+ReY2Oj1B7Rj6WB9l+Ot5v1XfJcEyW", + "O/01jTHrauRqsFz/0LbY9+tdX8+Ux3E6mgqeJss9H789OTlH8BKxNB4TUexxfzvrjzJFpkToDpOAjnAY", + "CiKlf/7uZRG2fr/fH+DtQb/f6/ugnBMWclGLUvPaj9KtfkhWdNkIpbb/JZS+uTg+Oj5Ah1wkXGD4dmmk", + "CmEX0VOcV5Fsyqvio//nKY3CZaof68dEjCiTCrMaGjy2LzW6+ASpGUH2O3RxgtoTLlBIxul0Stl0owm9", + "a4YVEUXCEVbLwwGoyLahnCFFYyIVjpNWpzXhItYftUKsSFe/aTSgIHjNcLpFo8GWt1pqVnIUy7reXRNE", + "GYppFFFJAs5CWRyDMrW3Wz+ZwoYhQnAPh3qhH6OYSImnBLU129S8myGpsEolohJNMI1I2GiNfIRgJvN3", + "PkY0JEzRCS3vb0NOXTwOtrZ3vLwjxlMyCunUnkTl7o/guSYx3Y9C0No/Eb3RFs3mAUMKMlke7yWwbhhE", + "kAkRRNP4Vw6XCD4nTO8WPd6fYNzW/9rMj+hNez5vAjJP8+ZfOq0/UpKSUcIlNRAucS77RpMRoBrBF36Y", + "4dWqtS5QlFRYrN4f0OIOdqKBrxFuzkzTL52WwtO1n7zXbaq8E1ijHbLEBWpZ5Is5YR4hKeBM2Rdl7Lzm", + "UxRRRpBtYddC80Q9wC8RB5Z4R3jI0L+8+TXct2Be5kFNb/pdp0VYGmtkRnxaxOaMYKHGpITMmiPMdpRD", + "V4v+09L2qZxVWJLRag5yShkjIdIt7cY2LVEqQVJdmj7soiuqRnMipHfPAVi/UYVsi9quIh5cTWhERjMs", + "ZwZiHIawX3F0WpqJR1orib840UzQdQhShESKo7NfD7af7CE7gAeHkqciMBAsz6Twte7etEUKizGOIi9t", + "1JPbzc/oZQrxU8BZtjHqzp6MAh1hGk7Xsqupu++0klTOzC/g3RoqOPs0G9DkFenfHzyTPgQmYbSEWp3J", + "LwO+Tcxio2nENU4XKGX0j7QkYPfQsdYVFNIHBQ1J2EEYXmiWjVPFu1PCiNB8Ck0Ej0HaKgjBqE16014H", + "DbVc2NVScBdvd/v9bn/YKoux0W53mqQaFVgpIjSA/+933P100P1bv/vsQ/5z1Ot++OuffATQVDJ3UqGd", + "Z9vt/Q5ywBbF9Sqg60T5W3P/Ivg+jmOW+ljziZuu9OHxsuBg5hry4IqIHuWbER0LLBabbErZx0GEFZGq", + "PPPVbe8UFzCPFUhgU42mG6KhovQAGbcjfk1EoDlwRDThyY5mwlTJDsJabwbmhfQp+e8owEzvBSNccIEI", + "C9E1VTOEoV0ZW/GiixPapQbUVqcV44+vCZuqWWuwt7NE55rI2/ZH98Nf3KON//CSukgj4iHydzxVlE0R", + "vDan+oxKlMNAFYnXrojDbhqBmBdTdmw+28ogwULgxdevsJvIqpU2ylztUgexR/J/OydC0NCdqocnR6gd", + "0StiyR2JlKFh2u/vBNAAfhL7JOBxjFlonm300NuYKn2apfkhbaxBveJy/94iwYyDnBFFXE8oQ3WNEJPj", + "MBAE9BMcrTyGV6HYi6zDrN/lQ/tXLlU3xgxPCWiTtiEaC35FNKAo4RENKJHoiiy0kLJAU91pd04l1duH", + "sDmaY2M06A3Z+xmXxDRxr7QiEhA6JyjmwRVKIhyQGQdFfI6jlMgOup5piUEzY0FwZB8jQWJM2ZDNNJAy", + "4AkJtQ5hmsHU0CVh80sU4wR2KRYEtiiKsSKC4oh+IiHi5pOYhFQfUENGgK5RgvWWDQIu9Omr15bgYFbA", + "wp8lujTyxiV0f0mZpspLs696Q1Zc+c+tt+fvn789f3M0env64s3B8ei3F/+tH5uPWoPfP7eMfTMTNJ4T", + "LIhAf/oM8/1ipNOQiNagdZCqGRf0kzG2fOm0NA6kpi+c0B5PCMO0F/C41Wn9pfjnhy8fnDylhyJsrreB", + "B7AvXlnGHIUejnLkjHkSWQMRiHYYTLXAYV6dnm/qwzXBUqqZ4Ol0Vt4Y9mS/0ZYIqbwaUT4aJz6YqLxC", + "x5tvkZY7UET1Bs3kjK1+/+T5phy29B9P3B8bPXRkdi2Ar1kIF1b8kTNNPloIB5I5PD1HOIp4YE0gE60r", + "Teg0FSTsVSxv0LuPPxOmxCLh1KeDVZhT3nSZR3W7+dsbsKLNMWWbUi9DN7gZ3oFubq0JvGBzKjiLtTY2", + "x4LqY1aW98qbt0cvRi/eXLQGmo+HaWCNiqdv371vDVo7/X6/5SNQTUFreOCr0/NDWCmzbVQSpdORpJ88", + "ksBBNj8Uk5gLowHbb1B7VhYUzL5FsDjD1s6r54a4tl4BXblFCamE1q4X03GZYrZfPfdRy2yREDGn0mcm", + "+zV751a+cKwbdl+mbUnEnIiMaIGKewX1I4h4GnYLQ3ZaEypIILAmu1an9QeJtRw+/6RJJ4fd853fetVI", + "/lwjWOIooYyskCy/EwnvmouriOOwu3XHAh4jSve9PMU35kV5fS1NkIwkWp0lawQLr2moZqOQXzMNsoev", + "2jcoa5wx1496Jjj61z/+eXGSq0lbr8aJ5bRb20++ktNWeKvu2msCySaSJv5pnCf+SVyc/Osf/3Qz+baT", + "MILIrYQ6u/4vTA/AsjWth6VrSmPNLKPlv2ZEzYgonN6OWPQjow/D58jRXmEqJfNo8U5ziVHzORERXhQY", + "r4WptdUH7leBSlAFe9V+p9noFdIfr2HDujd3yL+q6ujbfT+j9QDlgem55hX2XGgCSQbI1vaJ/bm9DFIN", + "RFc0GYHUPMLTzGS76rb57IomVhSHL8wyRpFhBGEKwvuYc9Ubsv+aEYZg7WCByUcSAM+TCit0cHos0TWN", + "IjDwAFNZPlq0YJ+zFdNcKv1fkbIOGqdKS+tcEWT1JhgkBVig8ZiglGF3nV2Rne0Eq3Rl0XJFBCPRyMjG", + "siFmzEfIflSLHJjqBEtFhOH2aVLG19FvJ2eofbRgOKYB+s30esLDNCLoLE00P9goY68zZIkgc61CsCkY", + "G6kdl08QT1WXT7pKEOJAjKGzzERm71rnr07P7W293OgN2TuiEUtYSEKA2Z04EqkZVijk7M96x5Kw3G1x", + "/ArS/Xv5Jqp8pzUPkrS8ItvV1XgD9+l67nMqVIojzSpL0qD3et04bnikfuMXUtQ+LNvKiBOr8r1oU3uH", + "6Rm8OJZlYr/Zwgg6jc0WBU18yYDh1MTPzYBd0/8xc4CsNNvkmuJXjHVmOqmiyPbdcTO7BZaOM5yUcYXv", + "Bj0HsqBZ15rFQyIVZYacdFtkBTqJ2pdaGbd0rNXvyw66/Evpgd66TjPQ4sE1MtgAdsD0o2L/VZvCWm2/", + "uU5XWRwsb78eB7LWzwjNt5ASmEl9NGoRKSE99CvwYKRInGhGxKaISiQN7yQhYvz63xE3Mon7dMg0aNJ4", + "aVh0ZDYfSaeMsumGltL1uYLD0BiGJqlKhW43pzLHZpl0nPGlOoH3Bjpi2GmcSn2gBlEaEnTpDDSXZbFu", + "2XyzrNFZe86SgmJQAooJ6GpqM06VHl5POMYqmGk88VQZty07dVkGoGwkWnedaWHJLrpusf5nGbsoI9Wa", + "CyqMX0/OXrGAVa9gXqyz4lk5w29hvCILWHJnTcRL9sSiIdFv7hNE8mhO7KlZNEWOcXBljhLjOWGtkMae", + "aE2IevtXtqjXuLZuKTS+GqO/LOkvkxJYcO1kc4qxwrsx3y4yLqQnZ8braL1WEkA+aA4DBNLUZceoOgQM", + "CIhpYolQSAUJ1FL3lE2HDDw4Lu2Tnu3tUm9yLWL4NqFPV/GKcgVlxXxTWlpUWFkntUE3emo8pkqRsFOW", + "Da4ISeT6SWnp2NqdPcZxQa4FdYzM2nvChtIVYRMuAhJbGf/r9L4Xhc68WtjNulh2qDD4LcBs6QnhJIko", + "CY33jlkPsJJKu05gIq167IYVpctc4JeHvMRRdInattEGEkTPRbq1YpzlxP7+8NSRQHbpfHHS0RSpucDl", + "TKlkpP8jR3oXX1Y7s9+6Ha6702eSRPt9UI92d3fsqlqbmQG40m3ZPOZ1SqhfmjOGEznjqvZe64qycB2h", + "uE5+021rjWKZQCNt8/u2iyWCdNNkKjA4pt6lVezWt42AzXrOu8bn3OdcmGE1SKXiccHFELUrjhG07EJR", + "RtacR90QKwwWxIZmTgPusrtuvDBdGR2qzgAymo493jb0k+aWaEqneLxQZbP9Vt+nqX3t1a+DxbcsdW7v", + "RvMj4Ujx1Y6/dIJc2yZ+fnAOjBQfzSfU03N2HOVeI1SioOJjb/VR3UU3CajV4kE2CWbGL9MgAYS9i5Pi", + "lVlvyLpwbA7QUTZA1m3WJQaZEIfmwqLNRQEICs5eaLzYQBhdnPTQ+wzaP0ukFY05cXEAMyzRmBCGUrD4", + "winWNWdoEYBUwmGnqp9bk4UJGdiAm0Fu3/XQr4uExNiaf/RWiLGiATgYjWllPnCMmIWyV7GYFY1PjYxF", + "q9yl35EplUpUnKVR+93Lw52dnWdVs+H2k25/q7v15P1Wf9DX//9bc7/qu4+K8PV1UOYt1mWryH0Oz4+P", + "tq2NsjyO+rSLn+1//IjVsz16LZ99isdi+vcd/CBxE35WdpT7mqF2KonoOjapqcrnYVZw5KrxILu1Y9g9", + "+Xnlbqur2hpMvNct7yMgxOdqbB1dbx6yUWWYa52VC5Nb1sAXCeiL+S4pSF7WJzCgXu/HIyqvnguCr0J+", + "zTzndoynRI7MeeZ3I0il8W0hH61VQnCuJtJcV5atlVu7T3f3d/Z29/t9TxzEMsHzgI4CfQI1AuDt4TGK", + "8IIIBN+gNtwzhWgc8XGZ0J/s7O0/7T/b2m4Kh7lZaYaHTGFyX6G2xchfXUyde1MCanv76d7Ozk5/b297", + "txFU1s7bCChnEy6JJE93nu5u7W/vNsKCTxB/4eJSqr7zoc9jQOs95o6vKxMS0AkNEES2IP0BasdwhJHs", + "kqi8J8c4HFmjh//sUJhGcqWjghnMtjQGsjiNFE0iYt7BgjSyIcPMj6AnnxMIZYyIURa2c4OebDTP2ot5", + "N5esCSpFZZVQd0IlSCG58ERJFA7MDl3L52A1c8A+1NGBnUNDanitVaduROYkKhKBObo0sDEXBGV0Yhat", + "NCvK5jii4YiyJPWSRC0qX6YCZFHTKcJjnipzuwcLVhwEfIVB95hodt1MP33JxdVar0t9Eo9EypjuZq01", + "5wAM4BNrYoFTHCP7tXPsLwh92S2cuau07yV6Z74wlp38cZIqRJniWjtl4XjRgZGsBYghQaTiwEmtoc92", + "01S69MstYOR0XhdmvJx3PpDLSXdibunvVsMWU6JGUmG1VmLRlPIe2p9B88ZO3PrDtQaQBnhn5PohkA5e", + "7l1Ntl3JcHI/GF/lA5bZGvJGcAoLGpIegt0Fziguqq6y084UTxISZvaf3pCdma2SPZLm5kN/aPCgZoQK", + "xAWd0vLAZcPYfTqT3YQUHTXdmhyLHy5LqPASvCbqNz2eKCIMBl3AcDHqxy5Cq9OyuG91WpYTlVHjHnow", + "kns4LoH46vT8pi5hieATGnmmCy4I9q3VzJyz1Ovd/ll36/8Yx0dNbyCiUWbcFmIekl4lJh/aNzt5Xp2e", + "n9bBlCVEQEXoluaUOZp4OEfmj+AwYi+D7G2i1WAc+euDJRskl72f+WTZicAxGaeTCRGj2GNce6nfI9PA", + "eBRRhk6el+VZLTc31ZpPS4sDavMEBzaevRn2PQa5yjQ6BWx+8C/XO2KO4booOL1UwraxgXA99CZLQYFe", + "nZ5LlDsHeSx15eWtdVM/nS0kDXBkejRBrZQVDWxAnI0l5NP8Q2uK9MjJsVc2dBsBtefTJIVtePaue/z2", + "YjMOybxTggkcemY8IhrujQK3mLtYuNynvsQk5nWWDkMYsukGKuAq28GNkVTYrx7sKK5wNJIR9zlZvNcv", + "EbxE7YuXJlZJQ9BBSWkp9fMCFkr0vefdMZoj1Q17BgNWTaalDe7VHcuZW4x5pTC90qC+rfIrwZFJWFOm", + "5zys2i08vyovNL9au3ttJ75xj50/doOYqcOTIyMwBJwpTBkRKCYK2/Q4BdcUEIdanVZXn1EhJjF4uE3+", + "fbVXSo0JvhgEVWvEPVzKdnEvBtyaKO13xnUgRDFmdEKkslHapZHlDG8/2RuYXBIhmew+2ev1ejcNDXmR", + "x4I0WopN4zlfiBLpydnXrcM9RIA0mcvn1unB+19bg9ZmKsVmxAMcbcoxZYPC39mf+Qv4Yf4cU+aNHGmU", + "foROltKOlK809Zllng/0TJh15dK0xEGBX3vFVKPPgEcChKt5o3QVnmr9xFDc14bj3jphR541ShUSdRQd", + "ORsk7aCfVltCnWAEbeyYKVM0yvOZLNtAb5WRRq4M2l8K2E8Iy8L0o8j8Cjib613hi9kvMXD37qvuD6x3", + "yiikHkr+L6vtGecGCGZav99amzhJ1pOtX1DM+F/TXCU2othzEn1zrn+bO7by6G+n//nH/5WnT/++9cfr", + "i4v/nr/6z6M39L8votO3XxW4tDqY/JtGhN9ZEDhcLJUiwZuS0glWgUegmnGpajBs3yDFjZ9lDx2C4jcY", + "si56TRUROBqgYavi2jtsoTb5iANlvkKcId2VDTDY0B+fGvOP/viz0y2/VPsIbSSBsAuSBRDJdBzyGFO2", + "MWRDZvtCbiIS7vT1rxAFOFGpIHr1tAwbLdBY4CCPIMgH76DPOEm+bAwZaLjkoxJ6BgkWKst+4UYAorBQ", + "GZ8B25yELh7baMhDlp1LWTi2sdH0MiMI2OarnpJ+pHjVFy7KETD7fV/gOnhr6YWMqFQEHKozytZklLmR", + "of1+iVXs9/f7awX8jIZWkB/shOXclI4oG+wlQ8AwtGHc4FnWwJaueZPZI+jX9+9PNRr0v2fIdZTjIlti", + "o+QZ3z1pbIQqkgWvvY2WPyJEr27DCRkjGXwWNQjWeWHcOt+/PkOKiNg52rcDjc4JDfT84PqfSplqUqQY", + "HRyevNjoNUiuCbjN4F+xju+zGVaDMqzRrM4WmFG8xm8HHR+BW63dobkAB241L7lAkWEw+b4eoHNJyj6q", + "sFTmVt+sZLTILW/mBBi2NlyPSZVTDNC7TG7EGSiZg2RODK7LfF9Ct/bixfj8LPVe8acFbyarF1nWBh4+", + "WGXO3frErWcFq7e/B+Ow560/dsGmebO9XTSG6sH8pJGv/b1LKzs31VFvmhehHLpYCHvNUiM0z2lwH7kB", + "lvW1j1SNai/hkX5tr9ydVnJxgmZYsj8reFnRTbZ2njZKUqlHbXp9Xby45hMDUrarXBxkdu1qIkKvaBQZ", + "bwZJpwxH6Blqnx2/+u349esN1EVv355Ul2LVF771aZAiwZH2q9NziFLBcuRugOqdHnHuOEw+Uqnkcpho", + "o4vU1SkZfi2lTfDG3W7cYS4Fd/u8NI2HyJLwLd36vr8MDStzKnxtYgQr7N5TXoRa5urLKVDms+bx3WY4", + "uBdwSjE7Pv5QlAmcz/WtUwp0WtTjb3ogNQskITo+zTML5kYp131lTs+2e1t7+72tfr+31W9iootxsGLs", + "k4PD5oP3t40hYoDHgyAckMlXmAgtYRvhDUfXeCHR0InXw5aR5wuCfGHbWhG80fXrcuaG2yVqqAoU61Ix", + "3CT1QrOcCivSA5+VEwM3ltGe/O2rcgiTpiezdV2wX41uYrwmKOBpFGo5aKx3nlGrSGi1P0lUnnMZNus5", + "u2L8mpWnbmyYev/+kRKxQBcnJyWLtyATm1K2wcTB5aFmHXhyo2XYXiMqr4XmlukNHiKlQZVrFk6rO09g", + "UDS5ORdKQ6ENTG+59Oi99qbMLI2mkxVzqhhNQjIfpalPKNKvXODE+fnxUYk4MN7b2u/vP+vuj7f2urth", + "f6uLt3b2uttPcH+yEzzdqUnq3tzt5faeLOXdXB+oBIgHA6SJQwsHer9lrijjVKHMTU1v5EMtXaKCGGvC", + "csAmcMyogsyHlE11N6CiWynXxEWa5IyUUQWB+JDFhTI9ZbCF6E6s89EAvYK28ArHEC7kgNC6TdkMgMOF", + "MYNqxuCGTuCv1SCfzVKlxS74Rs5ShfRfMG2NBqttrO7C8JgBesPhG+F8RBmvqi2mOfheLTevqjht6xXk", + "vEdhMMswB+hlxiQzNmvZalsS+9PwbuvYDE7bGyXXObviLU0t+coVvMI6LYPRVqflEAXeY8t+ZBYub4hE", + "kRR99wMER8BCcz+dVNHI5haAmVCpaGCUPgyLW7eTbRosEo7MCV5322ecP+wpn33kGMXFCWpDNOJfkdUJ", + "9V8b2c1gcVfubj/bfbb3dPvZXqOYgxzA9Qz+EFyTloFby+2DJB25ehk1Uz88PYezT5+rMo2Nkm/nXnDx", + "TAQPtLBJGcoLcOSDP+s9K4ZahDwdRwWjkY3LAn/+JtVSaq63/qDRnE4m7I9PwdX23wWNtz7uye2xVzfL", + "BvILssdFQ+eS1kfGXZO60O8NDwQlZG3AyDsiYQbojCgE9NNFOIBDOvMosiTnwkosxr2Etbuzs7P/9Ml2", + "I7qy0BU2zgjUz2UoTywEhS0GLVH73dkZ2iwQnOnTuVlCWgZmBTj/PkM2i3G/5IGpVZ8dH5XUyEs51di+", + "53Etyi+sEGQnZZEOjlGZgLS0y73Y3tnpP919sv+k2Ta2CtdIfFzNYVwqDIMemz2kuPJtMI6/PzhFuncx", + "wUFZwdja3tl9svd0/0ZQqRtBBZlvTMaKGwC2/3Tvye7O9lazyCefAdzG9JU2bJl3eTadhyg8q+FBxTLr", + "7dSdFj7Bc9kbc6UDaO5RWnUfvIm/cB7zTSX0Sguuqqit5bKijFuIW95oYubws0g9Tl0VLi2BNnXlXe25", + "e4rV7JhNuCehzw30TesP5SzfiZaDJNQcCQmjJHS8K1M8rWgFHlaRJChMicWcEZUEtgjH5pYHEvcwJ5NR", + "Ni37li8N2EQLNDCsjvCHcW3DJgYr6ffLeS9SwJUxMUuEcw+dRvZyKkd+RWW5Y0GmaYQFqrqrrwBZLuKI", + "sqsmvctFPOYRDZD+oGpNmPAo4tcj/Ur+AnPZaDQ7/cEov2CuWAcMcNa9wCxIZdx8Cr/oWW5UnJvg5N80", + "329CmcUm9j/vrdNLrTsZj+5zRj8WCL0cAru73a/ze6vptOTxthwNcFPebknWt+Odo/5BlqnWc7tp7o8q", + "SnFZDi7N1zdbuKBc5eW3LAmgtjMpuhDjMl4Lob6NDuJmd6RV47mDZlOSoDz67v6Tp3sNY62/StReUYju", + "KwTrebxCoK5ZqZMmUtv+k/1nz3Z2nzzbvpF85O5Zatan7q6luD6VhNQVme1JH/53I6DMTYsfpJrbljJA", + "peTStwboy4qtm8fY1Gjdq4rA5ivp1PyyAN5MxF0hLR2URK5C/YQ2mUxIoOicjAzeujkwFd+sRjAEOMEB", + "VQuPBoivTZ7OrEklVqRB7xVgPSi1fdtwP825ZDrO3QHabnD0F6PZVWhhv3HKBpmO67TIt9VRjQ5pc7NV", + "LBQNDAR5Ntjqnfx1hkx0jWXpUkH/DiDRXl4fo3r7ZFo0L+TnaD2r5Zffq/vinfx1+4rLX1nOgtZREpKr", + "GF91hNZvQS0RNM7j6zmRfaWB1vt0VPiDPQBv99VoXEymsjJbTSnzSn7q3nzcZpU9lr8zJ9jNxys4ENzk", + "w2peCaBHC4NFed53p0QSNdSkuFifBvAeosONSftW8eHWGv4gIeL28b2EhS8tx1nBC6q5z5/7yl+OuXSP", + "udft73T7e++3dgZP9gZbW/cRoJDdYdSZcp9+2rp+Gm3jyW60v3j6x9bs6XQ73vF6fdxD+slKEYVKNko7", + "h4SIakaQaiYdSSLKSFdm1x/rL6JXhB4Zo1yCFyDkrdDIbqIGuMKmK3btWXmSxc2LVY6cakL7h/BPs9Cv", + "1GWq4B8frQb7VvcJVUD8BFYFBeipGTAQMLd1p5lGwa4Ku8eLyJpJ+UimdB9fIuIPKzjYb3bj1nEq6xZu", + "IcyzU7jN5O4XS1STv15ClI/Frk6gUTmEzB1pMV9J5pJ6t9kz3tslrXNgLiR12X6yV87qctD9m8nigka9", + "weYvf/3f3Q9/+ZM/k1dJdZREdEMyAYn5iiy6JrW4Jq5eOfrVFOyTCtv8V4rgGLhdcEUMd43xxyK8T/qZ", + "LXvxBsdLUwBVI6Ys+3vthPxFdZcI7TwJm9TlvHtvcReNpDiKiZgSk/TJ1LUx9uGcBxA27w3ZqaAxFtSl", + "KdGaaSGb9xVZIMEVsIYBSmFapjtbvrIU8DReFL/NimNKC4FNSC74x4Wt9CD1iyFj5BpJEgiiHPwm5ktq", + "xDHlSfXfqLxkS151GbnuwgRI2L0iC89q+dbPOAvVpdbVGPeckq+pVIhPXOoEVGiM2iRO1MKFB7s7kY2b", + "OS8dZB3WVEi908CL/rO7CBM9XxkX+gMmdi76ljmA1nqVLa1/bTCW36h6VPXxNjcXNlll2Se5koJPqm69", + "zTXmKVMjuDpYto/qd+ZawgZCTtNqRojNmKlNG3a9HK1LcAg55FdeROW7zDladeGj9fcrK299CzMrQFK/", + "NsaxcDnacQWCTjVqrmdEkMJCwAd57OgNUWYvCdZHFhrHKq1JdKvZTE3CHUHh1sEiyCBWoyC7SFq+rVrt", + "G32CP2YjgAaB5ZK2A/MolKl99RySaL1zWS3pxHUBYFQLCT5fT0VN6mksL0aRqpbnbdp7N57lVSu4X93e", + "qhBnPkaJND/4TjNJglRQtTjTbMhGcEA1noPUkCHwJ5gEPM4Hh+haKH5M7c135eZda9M0QAenx7YWDwNZ", + "DF2coIhOSLAIImKDI5ccEsEe8vbwuGuiurMabXp4qgAhLiv6wekxJFkW0ozb7233oOQtlHdKaGvQ2ult", + "QcppjQaY4iYk44Cf9rpU70M49Y5Dezo/N030VwLHREHFvN89146KCJPcQ4LDCZ4WBNMEU2El0ySCy1Cj", + "UVP9LfiDOwY/MKdExyAcN3WslmphTcMkeWuX9YMmB5lwJs2Cbvf7JvMqU/Y4wHny3c2/S3N3mY/bSMoA", + "9Hico5ckTyfpWJR/6bR2+1s3gmdtvlzfsOcM26JOBMB8ckMk3GrQY2buq1zNYGIb5vsMSKi4w343hcbT", + "OMZi4dCV4yrhsk5EIxJhyNhpMsv8nY97yKruEM4pZzyNQqgXmZiaBJqNYqSw6E0/ISyCGZ2TIbOnh8l9", + "jAWEu8dInxpGci5vDTO0WX3DdohUz3m4qGA3625TdwfSVhnB1bAxSUbgPj+qSxuV2b0Syhikn82rdrv8", + "KUsc3eQLh5L3vlJlDDOVp582icK1GpMIMqEfvR028nPVDA+WhUBdiix9wvaG/4YdogH9zilH2Ttk0Vs+", + "5LSOYAusZZKAu/TBYoyjyJs1axrxMY5sPnWt7CwzcGhhkVIMnHRHLuMhMUFwyULNODO/03HKVGp+jwW/", + "lkTog9kGw1tcu3JShnShsAWNISDdpNrRY24aEDc/X5HFl96QHYSxS6Nky6ziSHKbaD4rOJYVDh6y2nDN", + "GrvXoS08Y5I8F/PiGjB5qpJU9ZCZCFE2gh+aQ9pkOSPhUOvQn4WpkrH4svk5H/ELSNQEh5pOCk3MlDY/", + "0/BLHdRyhPXsR9DUo5MQQMCwpU+XYUv/ngqsJepUzhAOwOtZPywuadtsbC5AWtmoYjjADCU8SSOnxdv8", + "+aU+IBsKjiKkYCu5b7UMBCtZMx/rLOFL7Wk9JczVdmUbQZLPwmbq7+7795OxD3j26X+evX2D4KjSa2DN", + "CJlRAnBkjA1ZaVs9em/IXuBgZuu9QfDGsEXDYSuTecMNgDWV9iqn2wXB6xcN2i9mmA4Nf+n1dFdGphug", + "3z+bXgZ6LyXxSPErwoatLx1UeDGlapaOs3cf/Aitu3A+KzEC1Da8f8PlsoICbfkxaM4NzELELa+NFgij", + "nAMVtfsxZVisTMTlQb3FoFYw8VQWkfF5CCa6YWswdEa6YaszbBE2h2fWkjdsffFjwCaOq48UMLnIbLOc", + "iPb6/Y31nmAWvx4RutRQb78vS9LX9p0JHlboWhY8zORcmJNeQZNVzohbDyD5PMeZwe2niLdGxLP6dEF4", + "g++L54Ah34gY239FAtMKeOQksJXaiSELiPMDjcP5bRqFgzoJLifeovpRVTKX1Yrdul0WAIiRo7/dB6A/", + "GDevzADjPnuocXFkaoi5POWPixxhsRwhdvwa8SuivgeK6z8UK3UFZL4h/T4W+nlFrNyXI63CzTahiH/R", + "3FKNXRcEx9L2YhprXfUMYOqeEabQC3jas/86jQdCfS8jPr0cIIPCiE9RRBmR1qcmu8PQh6LFJXxkUm1m", + "39lstcEMsymRqG3Oz3/9458AFGXTf/3jn1qaNr9gu2+aeA2IZL2cESzUmGB1OUC/EZJ0cUTnxE0GYs3I", + "nIgF2ulLWxdYv/LkvpVDNmTviEoFk/lNXMSngBPToS3FoudDWUokkoBCuLKb2BCCX/Kiwf69bFD5oDu6", + "s6Rz2RkUJqBPRUcD4BNKTTiv1b9afuuZmXPJfla14C7Z9NfzF0U+KkO9XQPgDRkMoNi37+CFnTRqn529", + "2Ogh0DEMVUCYCEjMeTdWeO795EnreZLhKGWGAlg2vKlQ9qDW/ntk2zQzANsefyQLcF0dh3oTsDF5EEFC", + "h6+fukITc7Afb8407LPPHrmyj/UG2tvPtziEc1dppAjf3To72lvGua1/mqPsW6jAqG3L0WUpSEtFVr8V", + "0T/IqVGozZsdHYibxKcPppYdcjaJaKBQ18ECWU5ikqlqZQJ5LOzgnYUaYTevakB28XzbLMUX1Z50WahR", + "fuTd/+lRGfQmx0geNJ7T2s+TZB3pHFEZcP1tgVq6AU5sAlYjvmT7tEhF6wxSR/A8O3JWiktHWdVuuyEf", + "zjRlh05Z9Wx4AKZ4VGGI35ARVpJKFtIsPCZqPs9W0ZW4XmG5+r5Is/9wUtBDW7F8ZP6YzFhhBW2aC86y", + "UmN15GWLkd3jQtsRPBM/I8LtagOoSVCYT8t8ioIZCa7MhGwl9lUSwbEr1t5E9TX9/Uiar6kCdwOJxaL8", + "p4jSQNnNcbVKwT22mTbvT7+FEW6k3t7dPa8lMA+Swdlk7CzWJokllgsWbPxQV70PcppVq70/op10mkaR", + "u/GYE6HyWnjFM2DzM7glrZft3W5beRycv3vdJSzg4IeW+VD5hShXoupuJXyzYGYqP8mkiU4IqHKEUS9A", + "f8X6G3dBlNU7+Lftl7biwb9tvzQ1D/5t58BUPdi4N2LpPxRrfmiJ+xETnxa4aRlpwJpMIal1EmrWqqGQ", + "6tr/UHKqLUp4E0k1w+tPYbWJsFpE10p5NasPeY8Sqy2l922uZDJi82HbRPNa/8QfTFJ9WCufpUiXUYbK", + "8rWHTRnKRV6+ztZsf3wOlDSjuOKx0dBcnW/IlceHI93jo46tTGjqCWYBIg9kvHZwPLhwa8d9eMv1QTym", + "05Snshh7AoUoibTBShEpM+DHJnbnx3Ot4P0dU2n/IY+OB5erf9L9PUn81QU1zNvcQK2T+V2rpjK/bQ8l", + "H001ERO79s5VKbFpcDZqnApdHZ+mZFwqObXs7OiDy6eLoHOtqOTqAgINYjBk/6H1j98VwfGHX1yQTNrv", + "b+/Bc8LmH35xcTLsxJEKYUpQIhEWBB28OYJrvylEr0MyuzwkrwqHSVFnanvbHCr/4xSk/OazuYbkqPCn", + "htRIQyqga7WGlFXBuU8VqZxl6cF1JEdvPoTb1Bo/taSH0JJkOpnQgBKm8gzOS05iNgH8I4wtY/Z+qODc", + "UTpoG2tJeWmq1QJonrbwwR17ssEfXjlyGRIfp488N1ExoVNH8sOwXh/53uih/7DM+eH1kMdMYkbgX0Zd", + "omXKZQIrZ0L8pjR29wKIP81jIwHkgWncZG58OAHk2PrKl4SQb7a3HkQUcVO2WiktQmJLaG48KndMk+wz", + "m4ckSlE2lfb21yTVLOb7tBk6NnxiyebEppP3qwsvubhqyiM8WZUfAasozvA71FQ0eKRQs/YbKiwgyput", + "pInmwVnIUqrsb+ngTatMJYjSkLJpXrWYqhlPTY6lkX1ocjTqXWFrg4ECFNhevzUP0qM/gDr6hitE4yQi", + "MYEcjl1DTVAqOk0SLrJqklQWEsvfjEfqbVN0tzeprmxN9w6y6ffBdO8WrA23eMvL5eWaEZ+uD7HPBnfx", + "5J4Y+yE7lybl06VRjC9RxmSR4kiSiAQKXc9oMIN4e/0M+jfh+DhJLrMEOxuu9HUxzxAM3pZE6KMg4Ezy", + "yFSfvpzH8eVgOR/kxckJfGRC7U3mx8sBcjkgswNC6lbF+Hk9iwhLhd7YrABtTUmCR5FZ0Ustkxbmt2Ej", + "6/MESEPmi7Jn5Np2SCfoshBwf1kTce8Y6ms+ld9Ksu3Up60zc1EcCUCcoU3CwladmZdG/lj7rX7fl02p", + "Ydy/AeOew/6XgHnNp1nKvBIp4yRpSr4WTKDieRyvoGHULpSmkCrkqfqrVCERAj621F1H3KiNA/OHwlea", + "UJktLOmKewD5eS8zTA4rL6o0Uy1UBzB/zeO41WlZeDy10L8+f0K1w2Wju16ZQpKEn3r3TdIflJl9If9B", + "5eSwVYjqRW5bXOmHt/5YRIU/gopatpbnUFDmRBVYW55XF3lUcdSm7lZVFjOlUHx7JCvcVb9LyldMZ3nR", + "lf+BKqqZa7Xa2gMrqRmKfZpZqVjRN9dOs9pJPzXUTEPlAoWpGa5SveyHVTszhoJSVtI8rXh6W90zSzmZ", + "oRmqyrKV14M5z9v87H4e30Jc+E44Yae2hlddcrN80t8Dy62pcPk93iA4OckeqwUB4RuyYFdr85tdM2h1", + "L+Ny3wUbNhsu48ZFnqMEZpK6Mro/mXHJDGgspbdlxk74XLIFFtgzZd0kwnV82cqptQzY1vT74fW1XFf5", + "wTW2gAthHEnBNfUxBTIXPAgKqmc7wakknWzDdJwXy8XJyUbdphFq5ZYR6hG7HlSKLMehv8q9oKErWXF4", + "cmQLXFCJRMp66G1MoY7EFSEJJKilPJWmymKvWH2wrqZlVl6QMCUWCadMrYUib3o/wHy5Vcr+B+ZTNpXD", + "D29WsmXTHxuTAt6hT287gdVKlTJFN73XdO7aijJTZ0MLH3jMU937UnVENKERkQupSGzu7CZpZGqyzkhw", + "ZXNB2++MJ2sHUSWR3g8d8PxLiIiplJQzOWRjMtFSSUKEHhvKB9OIFK4ffDdbZwpnXPPUsL7v42oLCibC", + "bQ5WdVgr10rESeJqJfquT7LyjrcG6SXcVSG5iMc8ogGKKLuSqB3RKyODo7lEkf6xsfKyawTf3XWm69vv", + "LI3pYzbh3mSghmYzYv4xfLvKbM1d5j86tvaKFDeL4z+w0H62JtfyNUFwBCWBM6d7lCoa0U+G1elOqFQ0", + "MBXUcIY7KP5kxusN2QlRQrfBgqCARxEJlLM1bCaCB5vDtN/fCRIK0VE7BIADhlf/OoYRD0/PoZ0pUNUZ", + "Mv0HdPz+4BRRjdMJtipzAVBG1DUXV+h48+2a6/8zQNP/YH3MTHCle6l3wX/e7N7co7p2D8maLcqTVQoQ", + "T354g4GV4H5aCx6ntQBCWrLZtKcCByAUy1mqQn7N/JYBUy9Zbn42P47XBUYpHMwuXOH470PatbWj1w3j", + "JvgoNqWdU0hMsuJvYq+35b0faXI3jTg3BRBiiiFe/lPgQP2I1H33l3VFPH6HN3UWoy4R+Heztx765LMw", + "uHjfIj4eyzY3lOZmAgVsi9anLLh5rW4WpEIQpiAxVC5aBjjBAVWLDsKRq61si6VlNqRuduSOBcFX+qTt", + "Ddm7LKzaFmvT2lXHqVYopPLK9GC1px56OydCpuMMOASMyeh5gHxbXjnAUWDqEpPJhASKzokpGCxrtK8M", + "lPtM0p0P4llo99Ki7rGpHH6agNXLycJqHSVPudpkLmdZq2bJXLJeC94wBU+RlT7PI9dwBCfRTUx2nsGv", + "aK1bvH11M++13/RHDccue0n5gbCvvnKWP0qOzLOCc0rTFDA5hT+2bCwFyEtbteTgtT4tRGOPrvv0sFqX", + "FiIb/KHTQpx5nXweWXI6XHLbqssH8f0RQv9hvYsfOh/E46YtLUrIJdTVc6IGkeDfBQXeTwj4N/auv0UI", + "+Hfl7wkhvN/O7/678vS0HouZp+fPIO/7dPA0kd4Q0Frn4Gm4nrU8r1SULmybZmqS7fFHkuCtsfIG8rtD", + "+88Ejg1UhgKy3ClcYTfA+6UleBInauGsUXwCfjd5hlFJP4H3ni9wLjM631+82i3ssXdHHo5Oa62xPxM/", + "PpjBN8+Of3z0+LM9Fvdc6WDZ1KdOF4tgRueleK1VO9iiKBGkm/AE7KyhQZjFhzvLFBa96Sdku+8N2fsZ", + "cX8h6rJlkBCFVJBARQtEmeLAEcwYf5ZIcK0JwHsuFj7zbXHnvhQ8PrCzWXMe2j1ljWG5m1+86IZY4e7c", + "cZsVJrSvuLI6wR9pnMbA8BBl6NVz1CYflTDJG9BEaz6ITjKUko8BIaEEmtwoArzVr7Fs0k9kNB03gXJF", + "Go63Ns0JClKpeOzW/vgItXGqeHdKmF4LLepPQJJNBJ/T0GTMzpE655HB6lYNQm9qd9VChfUHz5ULA9w3", + "kWGaHEjTTzQpswXj9tgatMaUYQBubcKL8p4yHrh6PEzBDy7fO45yWj+PsGrNfU2JWslxSFSco0hL9Bs/", + "j7nHfMwVPRncmVY67ZrlNG7m3NDQ5+A+8hlnji8Pa7a++H7u4ws1yh+h6XyeKaR1ZvPviwT7D3c+PLS5", + "/OIR+2+9Ik75LpjKoQPdo49gXvMARygkcxLxJNZipWnb6rRSEbUGrZlSyWBzM9LtZlyqwX5/v9/68uHL", + "/w8AAP//2K4m7EUwAQA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/openapi.yaml b/openapi.yaml index 1f0b5e1a..9701b33c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -203,6 +203,21 @@ components: $ref: "#/components/schemas/CreateInstanceRequestCredentialInject" minItems: 1 + UpdateInstanceRequest: + type: object + properties: + env: + type: object + additionalProperties: + type: string + description: | + Environment variable values to merge into the existing instance env. + Primarily used for credential key rotation: update the env vars + referenced by credential policies so the egress proxy injects the + new secret values on subsequent outbound requests. + example: + OUTBOUND_OPENAI_KEY: sk-new-rotated-key + CreateInstanceRequest: type: object required: [name, image] @@ -1718,7 +1733,56 @@ paths: application/json: schema: $ref: "#/components/schemas/Error" - + patch: + summary: Update instance settings (e.g. rotate credential secrets) + operationId: updateInstance + security: + - bearerAuth: [] + parameters: + - name: id + in: path + required: true + schema: + type: string + description: Instance ID or name + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateInstanceRequest" + responses: + 200: + description: Instance updated + content: + application/json: + schema: + $ref: "#/components/schemas/Instance" + 400: + description: Invalid request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 404: + description: Instance not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 409: + description: Invalid state (instance not running) + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + 500: + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /instances/{id}/standby: post: summary: Put instance in standby (pause, snapshot, delete VMM) From 57780139adf940867028846fe0b9e85782da0c43 Mon Sep 17 00:00:00 2001 From: sjmiller609 <7516283+sjmiller609@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:32:57 +0000 Subject: [PATCH 2/2] Fix CI: add UpdateInstance to mock and scope mapping - Add UpdateInstance stub to mockInstanceManager in lib/builds - Add PATCH /instances/{id} to RouteScopes (InstanceWrite) Co-Authored-By: Claude Opus 4.6 --- lib/builds/manager_test.go | 4 ++++ lib/scopes/scopes.go | 1 + 2 files changed, 5 insertions(+) diff --git a/lib/builds/manager_test.go b/lib/builds/manager_test.go index a3765d5f..69b41e0a 100644 --- a/lib/builds/manager_test.go +++ b/lib/builds/manager_test.go @@ -131,6 +131,10 @@ func (m *mockInstanceManager) StopInstance(ctx context.Context, id string) (*ins return nil, instances.ErrNotFound } +func (m *mockInstanceManager) UpdateInstance(ctx context.Context, id string, req instances.UpdateInstanceRequest) (*instances.Instance, error) { + return nil, nil +} + func (m *mockInstanceManager) StartInstance(ctx context.Context, id string, req instances.StartInstanceRequest) (*instances.Instance, error) { return nil, nil } diff --git a/lib/scopes/scopes.go b/lib/scopes/scopes.go index 1ebbc49a..53337345 100644 --- a/lib/scopes/scopes.go +++ b/lib/scopes/scopes.go @@ -229,6 +229,7 @@ var RouteScopes = map[string]Scope{ "POST /instances": InstanceWrite, "DELETE /instances/{id}": InstanceDelete, "GET /instances/{id}": InstanceRead, + "PATCH /instances/{id}": InstanceWrite, "POST /instances/{id}/fork": InstanceWrite, "GET /instances/{id}/logs": InstanceRead, "POST /instances/{id}/restore": InstanceWrite,