cmd/hub/api/application.go (347 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 (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/url"
"os"
"strings"
"github.com/epam/hubctl/cmd/hub/config"
"github.com/epam/hubctl/cmd/hub/util"
)
const applicationsResource = "hub/api/v1/applications"
var applicationsCache = make(map[string]*Application)
func Applications(selector string, showSecrets, showLogs, jsonFormat bool) {
applications, err := applicationsBy(selector)
if err != nil {
log.Fatalf("Unable to query for Application(s): %v", err)
}
if len(applications) == 0 {
if jsonFormat {
log.Print("No Applications")
} else {
fmt.Print("No Applications\n")
}
} else {
if jsonFormat {
var toMarshal interface{}
if len(applications) == 1 {
toMarshal = &applications[0]
} else {
toMarshal = applications
}
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("Applications:\n")
errors := make([]error, 0)
for _, application := range applications {
errors = formatApplicationEntity(&application, showSecrets, showLogs, errors)
}
if len(errors) > 0 {
fmt.Print("Errors encountered:\n")
for _, err := range errors {
fmt.Printf("\t%v\n", err)
}
}
}
}
}
func formatApplicationEntity(application *Application, showSecrets, showLogs bool, errors []error) []error {
title := fmt.Sprintf("%s [%s]", application.Name, application.Id)
if application.Description != "" {
title = fmt.Sprintf("%s - %s", title, application.Description)
}
fmt.Printf("\n\t%s\n", title)
if len(application.Tags) > 0 {
fmt.Printf("\t\tTags: %s\n", strings.Join(application.Tags, ", "))
}
fmt.Printf("\t\tKind: %s\n", application.Application)
if application.Environment.Name != "" {
fmt.Printf("\t\tEnvironment: %s\n", formatEnvironmentRef(&application.Environment))
}
if application.Platform.Name != "" {
fmt.Printf("\t\tPlatform: %s\n", formatPlatformRef(&application.Platform))
}
if len(application.Requires) > 0 {
fmt.Printf("\t\tRequires: %s\n", strings.Join(application.Requires, ", "))
}
fmt.Printf("\t\tStatus: %s\n", application.Status)
if len(application.StateFiles) > 0 {
fmt.Printf("\t\tState files:\n\t\t\t%s\n", strings.Join(application.StateFiles, "\n\t\t\t"))
}
resource := fmt.Sprintf("%s/%s", applicationsResource, application.Id)
if len(application.Outputs) > 0 {
formatted, errs := formatStackOutputs(resource, application.Outputs, showSecrets)
fmt.Printf("\t\tOutputs:\n%s", formatted)
if len(errs) > 0 {
errors = append(errors, errs...)
}
}
if len(application.Parameters) > 0 {
fmt.Print("\t\tParameters:\n")
}
for _, param := range sortParameters(application.Parameters) {
formatted, err := formatParameter(resource, param, showSecrets)
fmt.Printf("\t\t%s\n", formatted)
if err != nil {
errors = append(errors, err)
}
}
if len(application.InflightOperations) > 0 {
fmt.Print("\t\tInflight Operations:\n")
for _, op := range application.InflightOperations {
fmt.Print(formatInflightOperation(op, showLogs))
}
}
return errors
}
func formatApplication(application *Application) {
errors := formatApplicationEntity(application, 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 cachedApplicationBy(selector string) (*Application, error) {
application, cached := applicationsCache[selector]
if !cached {
var err error
application, err = applicationBy(selector)
if err != nil {
return nil, err
}
applicationsCache[selector] = application
}
return application, nil
}
func applicationBy(selector string) (*Application, error) {
if !util.IsUint(selector) {
return applicationByName(selector)
}
return applicationById(selector)
}
func applicationsBy(selector string) ([]Application, error) {
if !util.IsUint(selector) {
return applicationsByName(selector)
}
application, err := applicationById(selector)
if err != nil {
return nil, err
}
if application != nil {
return []Application{*application}, nil
}
return nil, nil
}
func applicationById(id string) (*Application, error) {
path := fmt.Sprintf("%s/%s", applicationsResource, url.PathEscape(id))
var jsResp Application
code, err := get(hubApi(), path, &jsResp)
if code == 404 {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("Error querying HubCTL Applications: %v", err)
}
if code != 200 {
return nil, fmt.Errorf("Got %d HTTP querying HubCTL Applications, expected 200 HTTP", code)
}
return &jsResp, nil
}
func applicationByName(name string) (*Application, error) {
applications, err := applicationsByName(name)
if err != nil {
return nil, fmt.Errorf("Unable to query for Application `%s`: %v", name, err)
}
if len(applications) == 0 {
return nil, fmt.Errorf("No Application `%s` found", name)
}
if len(applications) > 1 {
return nil, fmt.Errorf("More than one Application returned by name `%s`", name)
}
application := applications[0]
return &application, nil
}
func applicationsByName(name string) ([]Application, error) {
path := applicationsResource
if name != "" {
path += "?name=" + url.QueryEscape(name)
}
var jsResp []Application
code, err := get(hubApi(), path, &jsResp)
if code == 404 {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("Error querying HubCTL Applications: %v", err)
}
if code != 200 {
return nil, fmt.Errorf("Got %d HTTP querying HubCTL Applications, expected 200 HTTP", code)
}
return jsResp, nil
}
func InstallApplication(req ApplicationRequest, waitAndTailDeployLogs bool) {
if req.Platform != "" && !util.IsUint(req.Platform) {
platform, err := stackInstanceByDomain(req.Platform)
if err != nil {
log.Fatalf("Unable to install HubCTL Application: %v", err)
}
req.Platform = platform.Id
}
reqBody, err := json.Marshal(&req)
if err != nil {
log.Fatalf("Unable to install HubCTL Application: %v", err)
}
RawInstallApplication(bytes.NewReader(reqBody), waitAndTailDeployLogs)
}
func RawInstallApplication(body io.Reader, waitAndTailDeployLogs bool) {
application, err := rawInstallApplication(body)
if err != nil {
log.Fatalf("Unable to install HubCTL Application: %v", err)
}
formatApplication(application)
if waitAndTailDeployLogs {
if config.Verbose {
log.Print("Tailing automation task logs... ^C to interrupt")
}
os.Exit(Logs([]string{"application/" + application.Id}, true))
}
}
func rawInstallApplication(body io.Reader) (*Application, error) {
var jsResp Application
code, err := post2(hubApi(), applicationsResource, body, &jsResp)
if err != nil {
return nil, err
}
if code != 200 && code != 201 && code != 202 {
return nil, fmt.Errorf("Got %d HTTP installing HubCTL Application, expected [200, 201, 202] HTTP", code)
}
return &jsResp, nil
}
func DeleteApplication(selector string, waitAndTailDeployLogs bool) {
err := deleteApplication(selector)
if err != nil {
log.Fatalf("Unable to delete HubCTL Application: %v", err)
}
if waitAndTailDeployLogs {
if config.Verbose {
log.Print("Tailing automation task logs... ^C to interrupt")
}
os.Exit(Logs([]string{"application/" + selector}, true))
}
}
func deleteApplication(selector string) error {
application, err := applicationBy(selector)
id := ""
if err != nil {
str := err.Error()
if util.IsUint(selector) &&
(strings.Contains(str, "json: cannot unmarshal") || strings.Contains(str, "cannot parse")) {
util.Warn("%v", err)
id = selector
} else {
return err
}
} else if application == nil {
return error404
} else {
id = application.Id
}
force := ""
if config.Force {
force = "?force=true"
}
path := fmt.Sprintf("%s/%s%s", applicationsResource, 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 Application, expected [202, 204] HTTP", code)
}
return nil
}
func PatchApplication(selector string, change ApplicationPatch, waitAndTailDeployLogs bool) {
application, err := patchApplication(selector, change)
if err != nil {
log.Fatalf("Unable to patch HubCTL Application: %v", err)
}
formatApplication(application)
if waitAndTailDeployLogs {
if config.Verbose {
log.Print("Tailing automation task logs... ^C to interrupt")
}
os.Exit(Logs([]string{"application/" + application.Id}, true))
}
}
func patchApplication(selector string, change ApplicationPatch) (*Application, error) {
application, err := applicationBy(selector)
if err != nil {
return nil, err
}
if application == nil {
return nil, error404
}
path := fmt.Sprintf("%s/%s", applicationsResource, url.PathEscape(application.Id))
var jsResp Application
code, err := patch(hubApi(), path, &change, &jsResp)
if err != nil {
return nil, err
}
if code != 200 && code != 202 {
return nil, fmt.Errorf("Got %d HTTP patching HubCTL Application, expected [200, 202] HTTP", code)
}
return &jsResp, nil
}
func RawPatchApplication(selector string, body io.Reader, waitAndTailDeployLogs bool) {
application, err := rawPatchApplication(selector, body)
if err != nil {
log.Fatalf("Unable to patch HubCTL Application: %v", err)
}
formatApplication(application)
if waitAndTailDeployLogs {
if config.Verbose {
log.Print("Tailing automation task logs... ^C to interrupt")
}
os.Exit(Logs([]string{"application/" + application.Id}, true))
}
}
func rawPatchApplication(selector string, body io.Reader) (*Application, error) {
instance, err := applicationBy(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 application by Id")
}
} else {
instanceId = instance.Id
}
path := fmt.Sprintf("%s/%s", applicationsResource, url.PathEscape(instanceId))
var jsResp Application
code, err := patch2(hubApi(), path, body, &jsResp)
if err != nil {
return nil, err
}
if code != 200 && code != 202 {
return nil, fmt.Errorf("Got %d HTTP patching HubCTL Application, expected [200, 204] HTTP", code)
}
return &jsResp, nil
}