cmd/hub/api/instance.go (877 lines of code) (raw):

// Copyright (c) 2022 EPAM Systems, Inc. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. //go:build api package api import ( "encoding/json" "errors" "fmt" "io" "log" "net/url" "os" "sort" "strings" "time" "github.com/epam/hubctl/cmd/hub/config" "github.com/epam/hubctl/cmd/hub/util" ) const stackInstancesResource = "hub/api/v1/instances" var ( stackInstancesCache = make(map[string]*StackInstance) queryMarkers = []string{"<", ">", "="} ) func StackInstances(selector, environmentSelector string, showSecrets, showLogs, showBackups, jsonFormat bool) { environmentId := "" if environmentSelector != "" { environment, err := environmentBy(environmentSelector) if err != nil { log.Fatalf("Unable to query for Environment: %v", err) } if environment == nil { log.Fatalf("No Environment `%s` found", environmentSelector) } environmentId = environment.Id } instances, err := stackInstancesBy(selector, environmentId) if err != nil { log.Fatalf("Unable to query for Stack Instance(s): %v", err) } if len(instances) == 0 { if jsonFormat { log.Print("No Stack Instances") } else { fmt.Print("No Stack Instances\n") } } else { if jsonFormat { var toMarshal interface{} if len(instances) == 1 { toMarshal = &instances[0] } else { toMarshal = instances } out, err := json.MarshalIndent(toMarshal, "", " ") if err != nil { log.Fatalf("Error marshalling JSON response for output: %v", err) } os.Stdout.Write(out) os.Stdout.Write([]byte("\n")) } else { fmt.Print("Stack Instances:\n") errors := make([]error, 0) for _, instance := range instances { errors = formatStackInstanceEntity(&instance, showSecrets, showLogs, showBackups, errors) } if len(errors) > 0 { fmt.Print("Errors encountered:\n") for _, err := range errors { fmt.Printf("\t%v\n", err) } } } } } func formatStackInstanceEntity(instance *StackInstance, showSecrets, showLogs, showBackups bool, errors []error) []error { title := fmt.Sprintf("%s / %s [%s]", instance.Name, instance.Domain, instance.Id) if instance.Description != "" { title = fmt.Sprintf("%s - %s", title, instance.Description) } fmt.Printf("\n\t%s\n", title) if len(instance.Tags) > 0 { fmt.Printf("\t\tTags: %s\n", strings.Join(instance.Tags, ", ")) } if instance.Environment.Name != "" { fmt.Printf("\t\tEnvironment: %s\n", formatEnvironmentRef(&instance.Environment)) } if instance.Platform != nil && instance.Platform.Name != "" { fmt.Printf("\t\tPlatform: %s\n", formatPlatformRef(instance.Platform)) } if instance.Stack.Name != "" { fmt.Printf("\t\tStack: %s\n", formatStackRef(&instance.Stack)) } if instance.Template.Name != "" { fmt.Printf("\t\tTemplate: %s\n", formatTemplateRef(&instance.Template)) } if len(instance.ComponentsEnabled) > 0 { fmt.Printf("\t\tComponents: %s\n", strings.Join(instance.ComponentsEnabled, ", ")) } if len(instance.Verbs) > 0 { fmt.Printf("\t\tVerbs: %s\n", strings.Join(instance.Verbs, ", ")) } if g := instance.GitRemote; g.Public != "" { templateRef := "" if g.Template != nil && g.Template.Ref != "" { templateRef = fmt.Sprintf("\n\t\t\tRef: %s", g.Template.Ref) } k8sRef := "" if g.K8s != nil && g.K8s.Ref != "" { k8sRef = fmt.Sprintf("\n\t\t\tstack-k8s-aws ref: %s", g.K8s.Ref) } fmt.Printf("\t\tGit: %s%s%s\n", g.Public, templateRef, k8sRef) } if instance.Status.Status != "" { fmt.Printf("\t\tStatus: %s\n", instance.Status.Status) } if len(instance.StateFiles) > 0 { fmt.Printf("\t\tState files:\n\t\t\t%s\n", strings.Join(instance.StateFiles, "\n\t\t\t")) } if len(instance.Provides) > 0 { formatted := formatStackProvides(instance.Provides, "\t\t\t") fmt.Printf("\t\tProvides:\n%s\n", formatted) } resource := fmt.Sprintf("%s/%s", stackInstancesResource, instance.Id) if len(instance.Outputs) > 0 { formatted, errs := formatStackOutputs(resource, instance.Outputs, showSecrets) fmt.Printf("\t\tOutputs:\n%s", formatted) if len(errs) > 0 { errors = append(errors, errs...) } } if len(instance.Parameters) > 0 { fmt.Print("\t\tParameters:\n") for _, param := range sortParameters(instance.Parameters) { formatted, err := formatParameter(resource, param, showSecrets) fmt.Printf("\t\t%s\n", formatted) if err != nil { errors = append(errors, err) } } } if instance.Status.Template != nil && instance.Status.Template.Commit != "" { t := instance.Status.Template commit := t.Commit if len(commit) > 7 { commit = commit[:7] } fmt.Printf("\t\tTemplate deployed: %s %s %s %v %s\n", commit, t.Ref, t.Author, t.Date, t.Subject) } if instance.Status.K8s != nil && instance.Status.K8s.Commit != "" { t := instance.Status.K8s commit := t.Commit if len(commit) > 7 { commit = commit[:7] } fmt.Printf("\t\tKubernetes deployed: %s %s %s %v %s\n", commit, t.Ref, t.Author, t.Date, t.Subject) } if len(instance.Status.Components) > 0 { fmt.Print("\t\tComponents Status:\n") for _, comp := range instance.Status.Components { fmt.Print(formatComponentStatus(comp)) } } if len(instance.InflightOperations) > 0 { fmt.Print("\t\tInflight Operations:\n") for _, op := range instance.InflightOperations { fmt.Print(formatInflightOperation(op, showLogs)) } } if showBackups { backups, err := backupsByInstanceId(instance.Id) if err != nil { errors = append(errors, err) } if len(backups) > 0 { fmt.Print("\tBackups:\n") errs := make([]error, 0) for _, backup := range backups { errs = formatBackupEntity(&backup, showLogs, errors) } if len(errs) > 0 { errors = append(errors, errs...) } } if instance.Platform == nil { backups, err = backupsByPlatformId(instance.Id) if err != nil { errors = append(errors, err) } if len(backups) > 0 { fmt.Print("\tOverlay backups:\n") errs := make([]error, 0) for _, backup := range backups { errs = formatBackupEntity(&backup, showLogs, errors) } if len(errs) > 0 { errors = append(errors, errs...) } } } } return errors } func formatStackInstance(instance *StackInstance) { errors := formatStackInstanceEntity(instance, false, false, false, make([]error, 0)) if len(errors) > 0 { fmt.Print("Errors encountered formatting response:\n") for _, err := range errors { fmt.Printf("\t%v\n", err) } } } func cachedStackInstanceBy(selector string) (*StackInstance, error) { instance, cached := stackInstancesCache[selector] if !cached { var err error instance, err = stackInstanceBy(selector) if err != nil { return instance, err } stackInstancesCache[selector] = instance } return instance, nil } func stackInstanceBy(selector string) (*StackInstance, error) { if !util.IsUint(selector) { return stackInstanceByDomain(selector) } return stackInstanceById(selector) } func maybeQuery(selector string) bool { for _, marker := range queryMarkers { if strings.Contains(selector, marker) { return true } } return false } func stackInstancesBy(selector, environmentId string) ([]StackInstance, error) { if !util.IsUint(selector) { if maybeQuery(selector) { return stackInstancesByQuery(selector, environmentId) } return stackInstancesByDomain(selector, environmentId) } instance, err := stackInstanceById(selector) if err != nil { return nil, err } if instance != nil { return []StackInstance{*instance}, nil } return nil, nil } func stackInstanceById(id string) (*StackInstance, error) { path := fmt.Sprintf("%s/%s", stackInstancesResource, url.PathEscape(id)) var jsResp StackInstance code, err := get(hubApi(), path, &jsResp) if code == 404 { return nil, nil } if err != nil { return nil, fmt.Errorf("Error querying HubCTL Stack Instances: %v", err) } if code != 200 { return nil, fmt.Errorf("Got %d HTTP querying HubCTL Stack Instances, expected 200 HTTP", code) } return &jsResp, nil } func stackInstanceByDomain(domain string) (*StackInstance, error) { instances, err := stackInstancesByDomain(domain, "") if err != nil { return nil, fmt.Errorf("Unable to query for Stack Instance `%s`: %v", domain, err) } if len(instances) == 0 { return nil, fmt.Errorf("No Stack Instance `%s` found", domain) } if len(instances) > 1 { return nil, fmt.Errorf("More than one Stack Instance returned by domain `%s`", domain) } instance := instances[0] return &instance, nil } func stackInstancesByQuery(query, environmentId string) ([]StackInstance, error) { return stackInstancesByField(query, environmentId, "query") } func stackInstancesByDomain(domain, environmentId string) ([]StackInstance, error) { return stackInstancesByField(domain, environmentId, "domain") } func stackInstancesByField(selector, environmentId, field string) ([]StackInstance, error) { var args []string if environmentId != "" { args = append(args, fmt.Sprintf("environment=%s", url.QueryEscape(environmentId))) } if selector != "" { args = append(args, fmt.Sprintf("%s=%s", field, url.QueryEscape(selector))) } path := stackInstancesResource if len(args) > 0 { path = fmt.Sprintf("%s?%s", stackInstancesResource, strings.Join(args, "&")) } var jsResp []StackInstance code, err := get(hubApi(), path, &jsResp) if code == 404 { return nil, nil } if err != nil { return nil, fmt.Errorf("Error querying HubCTL Stack Instances: %v", err) } if code != 200 { return nil, fmt.Errorf("Got %d HTTP querying HubCTL Stack Instances, expected 200 HTTP", code) } return jsResp, nil } func stackInstancesByPlatform(platformId string) ([]StackInstance, error) { path := fmt.Sprintf("%s/%s/overlays", stackInstancesResource, url.PathEscape(platformId)) var jsResp []StackInstance code, err := get(hubApi(), path, &jsResp) if code == 404 { return nil, nil } if err != nil { return nil, fmt.Errorf("Error querying HubCTL Stack Instances: %v", err) } if code != 200 { return nil, fmt.Errorf("Got %d HTTP querying HubCTL Stack Instances, expected 200 HTTP", code) } return jsResp, nil } func formatStackInstanceRef(ref *StackInstanceRef, resource string) (string, []error) { errors := make([]error, 0) parameters := "" if len(ref.Parameters) > 0 { formattedParameters := make([]string, 0, len(ref.Parameters)) for _, param := range sortParameters(ref.Parameters) { formatted, err := formatParameter(resource, param, false) formattedParameters = append(formattedParameters, fmt.Sprintf("\n\t\t\t%s", formatted)) if err != nil { errors = append(errors, err) } } parameters = fmt.Sprintf("\t\t\tParameters:%s", strings.Join(formattedParameters, "")) } stack := "" if ref.Stack.Name != "" { stack = fmt.Sprintf("\n\t\t\tStack: %s\n", formatStackRef(&ref.Stack)) } platform := "" if ref.Platform.Id != "" { platform = fmt.Sprintf("\n\t\t\tPlatform: %s [%s]", ref.Platform.Domain, ref.Platform.Id) } return fmt.Sprintf("%s / %s [%s]%s%s%s", ref.Name, ref.Domain, ref.Id, platform, stack, parameters), errors } func formatPlatformRef(ref *PlatformRef) string { stateFiles := "" if len(ref.StateFiles) > 0 { stateFiles = fmt.Sprintf("\n\t\t\tState files:\n\t\t\t\t%s", strings.Join(ref.StateFiles, "\n\t\t\t\t")) } provides := "" if len(ref.Provides) > 0 { provides = fmt.Sprintf("\n\t\t\tProvides:\n%s", formatStackProvides(ref.Provides, "\t\t\t\t")) } return fmt.Sprintf("%s / %s [%s]%s%s", ref.Name, ref.Domain, ref.Id, stateFiles, provides) } func formatStackProvides(provides map[string][]string, indent string) string { str := make([]string, 0, len(provides)) for _, k := range util.SortedKeys2(provides) { str = append(str, fmt.Sprintf("%s => %s", k, strings.Join(provides[k], ", "))) } return fmt.Sprintf("%s%s", indent, strings.Join(str, "\n"+indent)) } func formatStackOutputs(resource string, outputs []Output, showSecrets bool) (string, []error) { ident := "\t\t" str := make([]string, 0, len(outputs)) errors := make([]error, 0) for _, o := range outputs { brief := "" if o.Brief != "" { brief = fmt.Sprintf(" [%s]", o.Brief) } messenger := "" if o.Messenger != "" { messenger = fmt.Sprintf(" *%s*", o.Messenger) } comp := "" if o.Component != "" { comp = fmt.Sprintf("%s:", o.Component) } title := fmt.Sprintf("%7s %s%s:", o.Kind, comp, o.Name) formatted, err := formatParameterValue(resource, o.Kind, o.Value, showSecrets) if err != nil { errors = append(errors, err) } var entry string if strings.Contains(formatted, "\n") { maybeNl := "\n" if strings.HasSuffix(formatted, "\n") { maybeNl = "" } entry = fmt.Sprintf("%s ~~%s%s %s%s~~", title, brief, messenger, formatted, maybeNl) } else { entry = fmt.Sprintf("%s %s%s%s", title, formatted, brief, messenger) } str = append(str, entry) } return fmt.Sprintf("%s%s\n", ident, strings.Join(str, "\n"+ident)), errors } func formatComponentStatus(comp ComponentStatus) string { ident := "\t\t\t" origin := "" title := "" version := "" if meta := comp.Meta; meta != nil { if meta.Origin != "" && meta.Origin != comp.Name { origin = meta.Origin } if meta.Kind != "" && meta.Kind != meta.Origin { origin = fmt.Sprintf("%s/%s", origin, meta.Kind) } if meta.Title != "" { title = fmt.Sprintf(" - %s", meta.Title) } version = meta.Version } if version == "" && comp.Version != "" { version = comp.Version } var etc []string if origin != "" { etc = append(etc, origin) } if version != "" { etc = append(etc, version) } printEtc := "" if len(etc) > 0 { printEtc = fmt.Sprintf(" [%s]", strings.Join(etc, " ")) } message := "" if comp.Message != "" { sep := " " if strings.Contains(comp.Message, "\n") { sep = "\n" } message = fmt.Sprintf(":%s%s", sep, comp.Message) } timestamps := "" if t := comp.Timestamps; t != nil { timestamps = fmt.Sprintf(" (start: %v; duration %s)", t.Start.Truncate(time.Second), t.End.Sub(t.Start).Truncate(time.Second).String()) } str := fmt.Sprintf("%s%s%s%s - %s%s%s\n", ident, comp.Name, printEtc, title, comp.Status, timestamps, message) if len(comp.Outputs) > 0 { str = fmt.Sprintf("%s%s\t%s\n", str, ident, formatComponentOutputs(comp.Outputs, ident)) } return str } func formatComponentOutputs(outputs []ComponentOutput, ident string) string { keys := make([]string, 0, len(outputs)) values := make(map[string]ComponentOutput) for _, output := range outputs { keys = append(keys, output.Name) values[output.Name] = output } sort.Strings(keys) str := make([]string, 0, len(outputs)) for _, name := range keys { output := values[name] brief := "" if output.Brief != "" { brief = fmt.Sprintf(" [%s]", output.Brief) } str = append(str, fmt.Sprintf("%s%s: %+v", name, brief, output.Value)) } return strings.Join(str, "\n\t"+ident) } func formatInflightOperation(op InflightOperation, showLogs bool) string { ident := "\t\t\t" logs := "" if showLogs && op.Logs != "" { logs = fmt.Sprintf("%sLogs:\n%s\t%s\n", ident, ident, strings.Join(strings.Split(op.Logs, "\n"), "\n"+ident+"\t")) } initiator := "" if op.Initiator != "" { initiator = fmt.Sprintf(" by %s", op.Initiator) } options := "" if len(op.Options) > 0 { options = fmt.Sprintf("%s\tOptions: %v\n", ident, op.Options) } location := "" if op.Location != "" { location = fmt.Sprintf("%s\tLocation: %v\n", ident, op.Location) } platform := "" if op.PlatformDomain != "" { platform = fmt.Sprintf("%s\tPlatform: %v\n", ident, op.PlatformDomain) } description := "" if op.Description != "" { description = fmt.Sprintf(" (%s)", op.Description) } phases := "" if len(op.Phases) > 0 { phases = fmt.Sprintf("%s\tPhases:\n%s\t\t%s\n", ident, ident, formatLifecyclePhases(op.Phases, ident+"\t")) } return fmt.Sprintf("%sOperation: %s - %s %v%s%s %s\n%s%s%s%s%s", ident, op.Operation, op.Status, op.Timestamp.Truncate(time.Second), initiator, description, op.Id, platform, options, location, phases, logs) } func formatLifecyclePhases(phases []LifecyclePhase, ident string) string { str := make([]string, 0, len(phases)) for _, phase := range phases { str = append(str, fmt.Sprintf("%s - %s", phase.Phase, phase.Status)) } return strings.Join(str, "\n"+ident+"\t") } func CreateStackInstance(req StackInstanceRequest) { stackInstance, err := createStackInstance(req) if err != nil { log.Fatalf("Unable to create HubCTL Stack Instance: %v", err) } formatStackInstance(stackInstance) } func createStackInstance(req StackInstanceRequest) (*StackInstance, error) { if req.Template != "" && !util.IsUint(req.Template) { template, err := templateByName(req.Template) if err != nil { return nil, err } req.Template = template.Id } if req.Environment != "" && !util.IsUint(req.Environment) { environment, err := environmentByName(req.Environment) if err != nil { return nil, err } req.Environment = environment.Id } if req.Platform != "" && !util.IsUint(req.Platform) { platform, err := stackInstanceByDomain(req.Platform) if err != nil { return nil, err } req.Platform = platform.Id } var jsResp StackInstance code, err := post(hubApi(), stackInstancesResource, &req, &jsResp) if err != nil { return nil, err } if code != 200 && code != 201 { return nil, fmt.Errorf("Got %d HTTP creating HubCTL Stack Instance, expected [200, 201] HTTP", code) } return &jsResp, nil } func RawCreateStackInstance(body io.Reader) { stackInstance, err := rawCreateStackInstance(body) if err != nil { log.Fatalf("Unable to create HubCTL Stack Instance: %v", err) } formatStackInstance(stackInstance) } func rawCreateStackInstance(body io.Reader) (*StackInstance, error) { var jsResp StackInstance code, err := post2(hubApi(), stackInstancesResource, body, &jsResp) if err != nil { return nil, err } if code != 200 && code != 201 { return nil, fmt.Errorf("Got %d HTTP creating HubCTL Stack Instance, expected [200, 201] HTTP", code) } return &jsResp, nil } func DeployStackInstance(selector string, components []string, waitAndTailDeployLogs, dryRun bool) { _, err := commandStackInstance(selector, "deploy", StackInstanceLifecycleRequest{components}, waitAndTailDeployLogs, dryRun) if err != nil { log.Fatalf("Unable to deploy HubCTL Stack Instance: %v", err) } } func UndeployStackInstance(selector string, components []string, waitAndTailDeployLogs bool) { _, err := commandStackInstance(selector, "undeploy", StackInstanceLifecycleRequest{components}, waitAndTailDeployLogs, false) if err != nil { log.Fatalf("Unable to undeploy HubCTL Stack Instance: %v", err) } } func BackupStackInstance(selector, name string, components []string, waitAndTailDeployLogs bool) { resp, err := commandStackInstance(selector, "backup", &BackupRequest{Name: name, Components: components}, false, false) if err != nil { log.Fatalf("Unable to backup HubCTL Stack Instance: %v", err) } if waitAndTailDeployLogs { if config.Verbose { log.Print("Tailing automation task logs... ^C to interrupt") } code := Logs([]string{"backup/" + resp.Id}, true) showBackup(resp.Id) os.Exit(code) } showBackup(resp.Id) } func commandStackInstance(selector, verb string, req interface{}, waitAndTailDeployLogs, dryRun bool) (*StackInstanceLifecycleResponse, error) { instance, err := cachedStackInstanceBy(selector) if err != nil { return nil, err } if instance == nil { return nil, error404 } maybeDryRun := "" if dryRun { maybeDryRun = "?dryRun=true" } var jsResp StackInstanceLifecycleResponse path := fmt.Sprintf("%s/%s/%s%s", stackInstancesResource, url.PathEscape(instance.Id), verb, maybeDryRun) code, err := post(hubApi(), path, req, &jsResp) if err != nil { return nil, err } if code != 200 && code != 202 && code != 204 { return nil, fmt.Errorf("Got %d HTTP in response to %s HubCTL Stack Instance, expected [200, 202, 204] HTTP", code, verb) } if config.Verbose { log.Printf("Instance %s automation task id: %s", verb, jsResp.JobId) } if waitAndTailDeployLogs && !dryRun { if config.Verbose { log.Print("Tailing automation task logs... ^C to interrupt") } os.Exit(Logs([]string{instance.Id}, true)) } return &jsResp, nil } func DeleteStackInstance(selector string) { err := deleteStackInstance(selector) if err != nil { log.Fatalf("Unable to delete HubCTL Stack Instance: %v", err) } } func deleteStackInstance(selector string) error { instance, err := cachedStackInstanceBy(selector) id := "" if err != nil { str := err.Error() if util.IsUint(selector) && (strings.Contains(str, "json: cannot unmarshal") || strings.Contains(str, "cannot parse") || config.Force) { util.Warn("%v", err) id = selector } else { return err } } else if instance == nil { return error404 } else { id = instance.Id } force := "" if config.Force { force = "?force=true" } path := fmt.Sprintf("%s/%s%s", stackInstancesResource, url.PathEscape(id), force) code, err := delete(hubApi(), path) if err != nil { return err } if code != 202 && code != 204 { return fmt.Errorf("Got %d HTTP deleting HubCTL Stack Instance, expected [202, 204] HTTP", code) } return nil } func KubeconfigStackInstance(selector, filename string) { err := kubeconfigStackInstance(selector, filename) if err != nil { log.Fatalf("Unable to create HubCTL Stack Instance Kubeconfig: %v", err) } } func kubeconfigStackInstance(selector, filename string) error { instance, err := stackInstanceBy(selector) if err != nil { return err } if instance == nil { return error404 } path := fmt.Sprintf("%s/%s/kubeconfig", stackInstancesResource, url.PathEscape(instance.Id)) code, body, err := get2(hubApi(), path) if err != nil { return err } if code != 200 { return fmt.Errorf("Got %d HTTP fetching HubCTL Stack Instance Kubeconfig, expected 200 HTTP", code) } if len(body) == 0 { return fmt.Errorf("Got empty HubCTL Stack Instance Kubeconfig") } if filename == "" { filename = fmt.Sprintf("kubeconfig.%s.yaml", instance.Domain) } var file io.WriteCloser if filename == "-" { file = os.Stdout } else { info, _ := os.Stat(filename) if info != nil { if info.IsDir() { filename = fmt.Sprintf("%s/kubeconfig", filename) } else { if !config.Force { log.Fatalf("Kubeconfig `%s` exists, use --force / -f to overwrite", filename) } } } var err error file, err = os.Create(filename) if err != nil { return fmt.Errorf("Unable to create %s: %v", filename, err) } defer file.Close() } written, err := file.Write(body) if written != len(body) { return fmt.Errorf("Unable to write %s: %v", filename, err) } if config.Verbose && filename != "-" { log.Printf("Wrote %s", filename) } return nil } func LogsStackInstance(selector, operationId, filename string) { err := logsStackInstance(selector, operationId, filename) if err != nil { log.Fatalf("Unable to download HubCTL Stack Instance log: %v", err) } } func logsStackInstance(selector, operationId, filename string) error { instance, err := stackInstanceBy(selector) if err != nil { return err } if instance == nil { return error404 } ops := instance.InflightOperations if len(ops) == 0 { return errors.New("No inflight operations") } var op InflightOperation if operationId == "" { op = ops[len(ops)-1] } else { for i := range ops { if operationId == ops[i].Id { op = ops[i] } } } if op.Id == "" { return errors.New("No inflight operation found") } if op.Location == "" { return errors.New("Inflight operation has no location") } path := fmt.Sprintf("%s/%s?log=true", tasksResource, op.Location) code, body, err := get2(hubApi(), path) // TODO stream log into file if err != nil { return err } if code != 200 { return fmt.Errorf("Got %d HTTP fetching HubCTL Stack Instance log, expected 200 HTTP", code) } if len(body) == 0 { return fmt.Errorf("Got empty HubCTL Stack Instance log") } if filename == "" { filename = fmt.Sprintf("logs.%s.%s.txt", instance.Domain, op.Id) } var file io.WriteCloser if filename == "-" { file = os.Stdout } else { info, _ := os.Stat(filename) if info != nil { if info.IsDir() { filename = fmt.Sprintf("%s/%s", filename, op.Id) } else { if !config.Force { log.Fatalf("Log `%s` exists, use --force / -f to overwrite", filename) } } } var err error file, err = os.Create(filename) if err != nil { return fmt.Errorf("Unable to create %s: %v", filename, err) } defer file.Close() } written, err := file.Write(body) if written != len(body) { return fmt.Errorf("Unable to write %s: %v", filename, err) } if config.Verbose && filename != "-" { log.Printf("Wrote %s", filename) } return nil } func PatchStackInstanceForCmd(selector string, change StackInstancePatch, replace bool) { stackInstance, err := PatchStackInstance(selector, change, replace) if err != nil { log.Fatalf("Unable to patch HubCTL Stack Instance: %v", err) } formatStackInstance(stackInstance) } func PatchStackInstance(selector string, change StackInstancePatch, replace bool) (*StackInstance, error) { instance, err := stackInstanceBy(selector) if err != nil { return nil, err } if instance == nil { return nil, error404 } // reset `gitRemote` as we may have unmarshalled empty struct due to presence of `public` field // which is not allowed in patch if change.GitRemote != nil && change.GitRemote.Template == nil && change.GitRemote.K8s == nil { change.GitRemote = nil } maybeReplace := "" if replace { maybeReplace = "?replace=1" } path := fmt.Sprintf("%s/%s%s", stackInstancesResource, url.PathEscape(instance.Id), maybeReplace) var jsResp StackInstance code, err := patch(hubApi(), path, &change, &jsResp) if err != nil { return nil, err } if code != 200 { return nil, fmt.Errorf("Got %d HTTP patching HubCTL Stack Instance, expected 200 HTTP", code) } return &jsResp, nil } func RawPatchStackInstance(selector string, body io.Reader, replace bool) { stackInstance, err := rawPatchStackInstance(selector, body, replace) if err != nil { log.Fatalf("Unable to patch HubCTL Stack Instance: %v", err) } formatStackInstance(stackInstance) } func rawPatchStackInstance(selector string, body io.Reader, replace bool) (*StackInstance, error) { instance, err := stackInstanceBy(selector) if err != nil && !config.Force { return nil, err } if instance == nil && !config.Force { return nil, error404 } instanceId := "" if instance == nil { if util.IsUint(selector) { instanceId = selector } else { return nil, errors.New("Specify instance by Id") } } else { instanceId = instance.Id } maybeReplace := "" if replace { maybeReplace = "?replace=1" } path := fmt.Sprintf("%s/%s%s", stackInstancesResource, url.PathEscape(instanceId), maybeReplace) var jsResp StackInstance code, err := patch2(hubApi(), path, body, &jsResp) if err != nil { return nil, err } if code != 200 { return nil, fmt.Errorf("Got %d HTTP patching HubCTL Stack Instance, expected 200 HTTP", code) } return &jsResp, nil }