convox/pkg/cli/resources.go
2019-11-05 14:03:55 -05:00

769 lines
15 KiB
Go

package cli
import (
"fmt"
"io"
"net/url"
"os"
"sort"
"strconv"
"strings"
"github.com/convox/convox/pkg/options"
"github.com/convox/convox/pkg/structs"
"github.com/convox/convox/sdk"
"github.com/convox/stdcli"
)
func init() {
register("resources", "list resources", Resources, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack, flagApp},
Validate: stdcli.Args(0),
})
register("resources console", "start a console for a resource", ResourcesConsole, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack, flagApp},
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("resources export", "export data from a resource", ResourcesExport, stdcli.CommandOptions{
Flags: []stdcli.Flag{
flagRack,
flagApp,
stdcli.StringFlag("file", "f", "export to file"),
},
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("resources import", "import data to a resource", ResourcesImport, stdcli.CommandOptions{
Flags: []stdcli.Flag{
flagRack,
flagApp,
stdcli.StringFlag("file", "f", "import from a file"),
},
Validate: stdcli.Args(1),
})
register("resources info", "get information about a resource", ResourcesInfo, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack, flagApp},
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("resources proxy", "proxy a local port to a resource", ResourcesProxy, stdcli.CommandOptions{
Flags: []stdcli.Flag{
flagRack,
flagApp,
stdcli.IntFlag("port", "p", "local port"),
stdcli.BoolFlag("tls", "t", "wrap connection in tls"),
},
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("resources url", "get url for a resource", ResourcesUrl, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack, flagApp},
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("rack resources", "list resources", RackResources, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack},
Invisible: true,
Validate: stdcli.Args(0),
})
register("rack resources create", "create a resource", RackResourcesCreate, stdcli.CommandOptions{
Flags: []stdcli.Flag{
flagRack,
flagWait,
stdcli.StringFlag("name", "n", "resource name"),
},
Invisible: true,
Usage: "<type> [Option=Value]...",
Validate: stdcli.ArgsMin(1),
})
register("rack resources delete", "delete a resource", RackResourcesDelete, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack, flagWait},
Invisible: true,
Usage: "<name>",
Validate: stdcli.Args(1),
})
register("rack resources info", "get information about a resource", RackResourcesInfo, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack},
Invisible: true,
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("rack resources link", "link a resource to an app", RackResourcesLink, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagApp, flagRack, flagWait},
Invisible: true,
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("rack resources options", "list options for a resource type", RackResourcesOptions, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack},
Invisible: true,
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("rack resources proxy", "proxy a local port to a rack resource", RackResourcesProxy, stdcli.CommandOptions{
Flags: []stdcli.Flag{
flagRack,
stdcli.IntFlag("port", "p", "local port"),
stdcli.BoolFlag("tls", "t", "wrap connection in tls"),
},
Invisible: true,
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("rack resources types", "list resource types", RackResourcesTypes, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack},
Invisible: true,
Validate: stdcli.Args(0),
})
register("rack resources update", "update resource options", RackResourcesUpdate, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack, flagWait},
Invisible: true,
Usage: "<name> [Option=Value]...",
Validate: stdcli.ArgsMin(1),
})
register("rack resources unlink", "unlink a resource from an app", RackResourcesUnlink, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagApp, flagRack, flagWait},
Invisible: true,
Usage: "<resource>",
Validate: stdcli.Args(1),
})
register("rack resources url", "get url for a resource", RackResourcesUrl, stdcli.CommandOptions{
Flags: []stdcli.Flag{flagRack},
Invisible: true,
Usage: "<resource>",
Validate: stdcli.Args(1),
})
}
func Resources(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
if s.Version <= "20190111211123" {
return fmt.Errorf("command unavailable, please upgrade this rack")
}
rs, err := rack.ResourceList(app(c))
if err != nil {
return err
}
t := c.Table("NAME", "TYPE", "URL")
for _, r := range rs {
t.AddRow(r.Name, r.Type, r.Url)
}
return t.Print()
}
func ResourcesConsole(rack sdk.Interface, c *stdcli.Context) error {
opts := structs.ResourceConsoleOptions{}
if w, h, err := c.TerminalSize(); err == nil {
opts.Height = options.Int(h)
opts.Width = options.Int(w)
}
restore := c.TerminalRaw()
defer restore()
if err := rack.ResourceConsole(app(c), c.Arg(0), c, opts); err != nil {
return err
}
return nil
}
func ResourcesExport(rack sdk.Interface, c *stdcli.Context) error {
var w io.Writer
if file := c.String("file"); file != "" {
f, err := os.Create(file)
if err != nil {
return err
}
defer f.Close()
w = f
} else {
w = c.Writer().Stdout
c.Writer().Stdout = c.Writer().Stderr
}
r, err := rack.ResourceExport(app(c), c.Arg(0))
if err != nil {
return err
}
if _, err := io.Copy(w, r); err != nil {
return err
}
return nil
}
func ResourcesImport(rack sdk.Interface, c *stdcli.Context) error {
var r io.Reader
if file := c.String("file"); file != "" {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
r = f
} else {
r = c.Reader()
}
c.Startf("Importing data")
if err := rack.ResourceImport(app(c), c.Arg(0), r); err != nil {
return err
}
return c.OK()
}
func ResourcesInfo(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
if s.Version <= "20190111211123" {
return fmt.Errorf("command unavailable, please upgrade this rack")
}
r, err := rack.ResourceGet(app(c), c.Arg(0))
if err != nil {
return err
}
i := c.Info()
i.Add("Name", r.Name)
i.Add("Type", r.Type)
if r.Url != "" {
i.Add("URL", r.Url)
}
return i.Print()
}
func ResourcesProxy(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
if s.Version <= "20190111211123" {
return fmt.Errorf("command unavailable, please upgrade this rack")
}
r, err := rack.ResourceGet(app(c), c.Arg(0))
if err != nil {
return err
}
if r.Url == "" {
return fmt.Errorf("no url for resource: %s", r.Name)
}
u, err := url.Parse(r.Url)
if err != nil {
return err
}
remotehost := u.Hostname()
remoteport := u.Port()
if remoteport == "" {
switch u.Scheme {
case "http":
remoteport = "80"
case "https":
remoteport = "443"
default:
return fmt.Errorf("unknown port for url: %s", r.Url)
}
}
rpi, err := strconv.Atoi(remoteport)
if err != nil {
return err
}
port := rpi
if p := c.Int("port"); p != 0 {
port = p
}
go proxy(rack, c, port, remotehost, rpi, c.Bool("tls"))
<-c.Done()
return nil
}
func ResourcesUrl(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
if s.Version <= "20190111211123" {
return fmt.Errorf("command unavailable, please upgrade this rack")
}
r, err := rack.ResourceGet(app(c), c.Arg(0))
if err != nil {
return err
}
if r.Url == "" {
return fmt.Errorf("no url for resource: %s", r.Name)
}
fmt.Fprintf(c, "%s\n", r.Url)
return nil
}
func RackResources(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
var rs structs.Resources
if s.Version <= "20190111211123" {
rs, err = rack.SystemResourceListClassic()
} else {
rs, err = rack.SystemResourceList()
}
if err != nil {
return err
}
t := c.Table("NAME", "TYPE", "STATUS")
for _, r := range rs {
t.AddRow(r.Name, r.Type, r.Status)
}
return t.Print()
}
func RackResourcesCreate(rack sdk.Interface, c *stdcli.Context) error {
var opts structs.ResourceCreateOptions
if err := c.Options(&opts); err != nil {
return err
}
if v := c.String("name"); v != "" {
opts.Name = options.String(v)
}
opts.Parameters = map[string]string{}
for _, arg := range c.Args[1:] {
parts := strings.SplitN(arg, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("Name=Value expected: %s", arg)
}
opts.Parameters[parts[0]] = parts[1]
}
c.Startf("Creating resource")
s, err := rack.SystemGet()
if err != nil {
return err
}
var r *structs.Resource
if s.Version <= "20180708231844" {
r, err = rack.ResourceCreateClassic(c.Arg(0), opts)
} else if s.Version <= "20190111211123" {
r, err = rack.SystemResourceCreateClassic(c.Arg(0), opts)
} else {
r, err = rack.SystemResourceCreate(c.Arg(0), opts)
}
if err != nil {
return err
}
if c.Bool("wait") {
if err := waitForResourceRunning(rack, c, r.Name); err != nil {
return err
}
}
return c.OK(r.Name)
}
func RackResourcesDelete(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
c.Startf("Deleting resource")
if s.Version <= "20190111211123" {
err = rack.SystemResourceDeleteClassic(c.Arg(0))
} else {
err = rack.SystemResourceDelete(c.Arg(0))
}
if err != nil {
return err
}
if c.Bool("wait") {
if err := waitForResourceDeleted(rack, c, c.Arg(0)); err != nil {
return err
}
}
return c.OK()
}
func RackResourcesInfo(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
var r *structs.Resource
if s.Version <= "20190111211123" {
r, err = rack.SystemResourceGetClassic(c.Arg(0))
} else {
r, err = rack.SystemResourceGet(c.Arg(0))
}
if err != nil {
return err
}
// fmt.Printf("r = %+v\n", r)
i := c.Info()
apps := []string{}
for _, a := range r.Apps {
apps = append(apps, a.Name)
}
sort.Strings(apps)
options := []string{}
for k, v := range r.Parameters {
options = append(options, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(options)
i.Add("Name", r.Name)
i.Add("Type", r.Type)
i.Add("Status", r.Status)
i.Add("Options", strings.Join(options, "\n"))
if r.Url != "" {
i.Add("URL", r.Url)
}
if len(apps) > 0 {
i.Add("Apps", strings.Join(apps, ", "))
}
return i.Print()
}
func RackResourcesLink(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
c.Startf("Linking to <app>%s</app>", app(c))
resource := c.Arg(0)
if s.Version <= "20190111211123" {
_, err = rack.SystemResourceLinkClassic(resource, app(c))
} else {
_, err = rack.SystemResourceLink(resource, app(c))
}
if err != nil {
return err
}
if c.Bool("wait") {
if err := waitForResourceRunning(rack, c, resource); err != nil {
return err
}
}
return c.OK()
}
func RackResourcesOptions(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
var rts structs.ResourceTypes
if s.Version <= "20190111211123" {
rts, err = rack.SystemResourceTypesClassic()
} else {
rts, err = rack.SystemResourceTypes()
}
if err != nil {
return err
}
var rt *structs.ResourceType
for _, t := range rts {
if t.Name == c.Arg(0) {
rt = &t
break
}
}
if rt == nil {
return fmt.Errorf("no such resource type: %s", c.Arg(0))
}
t := c.Table("NAME", "DEFAULT", "DESCRIPTION")
sort.Slice(rt.Parameters, rt.Parameters.Less)
for _, p := range rt.Parameters {
t.AddRow(p.Name, p.Default, p.Description)
}
return t.Print()
}
func RackResourcesProxy(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
var r *structs.Resource
if s.Version <= "20190111211123" {
r, err = rack.SystemResourceGetClassic(c.Arg(0))
} else {
r, err = rack.SystemResourceGet(c.Arg(0))
}
if err != nil {
return err
}
if r.Url == "" {
return fmt.Errorf("no url for resource: %s", r.Name)
}
u, err := url.Parse(r.Url)
if err != nil {
return err
}
remotehost := u.Hostname()
remoteport := u.Port()
if remoteport == "" {
switch u.Scheme {
case "http":
remoteport = "80"
case "https":
remoteport = "443"
default:
return fmt.Errorf("unknown port for url: %s", r.Url)
}
}
rpi, err := strconv.Atoi(remoteport)
if err != nil {
return err
}
port := rpi
if p := c.Int("port"); p != 0 {
port = p
}
go proxy(rack, c, port, remotehost, rpi, c.Bool("tls"))
<-c.Done()
return nil
}
func RackResourcesTypes(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
var rts structs.ResourceTypes
if s.Version <= "20190111211123" {
rts, err = rack.SystemResourceTypesClassic()
} else {
rts, err = rack.SystemResourceTypes()
}
if err != nil {
return err
}
t := c.Table("TYPE")
for _, rt := range rts {
t.AddRow(rt.Name)
}
return t.Print()
}
func RackResourcesUnlink(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
c.Startf("Unlinking from <app>%s</app>", app(c))
resource := c.Arg(0)
if s.Version <= "20190111211123" {
_, err = rack.SystemResourceUnlinkClassic(resource, app(c))
} else {
_, err = rack.SystemResourceUnlink(resource, app(c))
}
if err != nil {
return err
}
if c.Bool("wait") {
if err := waitForResourceRunning(rack, c, resource); err != nil {
return err
}
}
return c.OK()
}
func RackResourcesUpdate(rack sdk.Interface, c *stdcli.Context) error {
opts := structs.ResourceUpdateOptions{
Parameters: map[string]string{},
}
for _, arg := range c.Args[1:] {
parts := strings.SplitN(arg, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("Key=Value expected: %s", arg)
}
opts.Parameters[parts[0]] = parts[1]
}
c.Startf("Updating resource")
s, err := rack.SystemGet()
if err != nil {
return err
}
resource := c.Arg(0)
if s.Version <= "20180708231844" {
_, err = rack.ResourceUpdateClassic(resource, opts)
} else if s.Version <= "20190111211123" {
_, err = rack.SystemResourceUpdateClassic(resource, opts)
} else {
_, err = rack.SystemResourceUpdate(resource, opts)
}
if err != nil {
return err
}
if c.Bool("wait") {
if err := waitForResourceRunning(rack, c, resource); err != nil {
return err
}
}
return c.OK()
}
func RackResourcesUrl(rack sdk.Interface, c *stdcli.Context) error {
s, err := rack.SystemGet()
if err != nil {
return err
}
var r *structs.Resource
if s.Version <= "20190111211123" {
r, err = rack.SystemResourceGetClassic(c.Arg(0))
} else {
r, err = rack.SystemResourceGet(c.Arg(0))
}
if err != nil {
return err
}
if s.Version <= "20180708231844" {
if u := r.Parameters["Url"]; u != "" {
fmt.Fprintf(c, "%s\n", u)
return nil
}
}
if r.Url == "" {
return fmt.Errorf("no url for resource: %s", r.Name)
}
fmt.Fprintf(c, "%s\n", r.Url)
return nil
}