github.com/section/sectionctl@v1.12.3/commands/apps.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "io" 6 "os" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/rs/zerolog/log" 12 13 "github.com/olekukonko/tablewriter" 14 "github.com/section/sectionctl/api" 15 ) 16 17 // AppsCmd manages apps on Section 18 type AppsCmd struct { 19 List AppsListCmd `cmd help:"List apps on Section." default:"1"` 20 Info AppsInfoCmd `cmd help:"Show detailed app information on Section."` 21 Create AppsCreateCmd `cmd help:"Create new app on Section."` 22 Delete AppsDeleteCmd `cmd help:"DANGER ZONE. This deletes an existing app on Section."` 23 Stacks AppsStacksCmd `cmd help:"See the available stacks to create new apps with."` 24 } 25 26 // AppsListCmd handles listing apps running on Section 27 type AppsListCmd struct { 28 AccountID int `short:"a" help:"Account ID to find apps under"` 29 } 30 31 // NewTable returns a table with sectionctl standard formatting 32 func NewTable(cli *CLI, out io.Writer) (t *tablewriter.Table) { 33 if cli.Quiet { 34 out = io.Discard 35 } 36 t = tablewriter.NewWriter(out) 37 t.SetBorders(tablewriter.Border{Left: true, Top: true, Right: true, Bottom: true}) 38 t.SetCenterSeparator("|") 39 t.SetAlignment(tablewriter.ALIGN_LEFT) 40 return t 41 } 42 43 // Run executes the command 44 func (c *AppsListCmd) Run(cli *CLI, logWriters *LogWriters) (err error){ 45 s := NewSpinner("Looking up apps",logWriters) 46 s.Start() 47 48 accounts, err := api.Accounts() 49 if err != nil { 50 s. Stop() 51 log.Error().Err(err).Msg("Unable to look up accounts"); 52 os.Exit(1) 53 } 54 s.Stop() 55 if c.AccountID != 0{ 56 newAct := []api.Account{} 57 for _, a := range accounts { 58 if a.ID == c.AccountID{ 59 newAct = append(newAct, a) 60 } 61 accounts = newAct 62 } 63 if(len(newAct) == 0){ 64 log.Info().Int("Account ID",c.AccountID).Msg("Unable to find accounts where") 65 os.Exit(1) 66 } 67 } 68 fmt.Println() 69 fmt.Println() 70 for _, acc := range accounts { 71 log.Info().Msg(fmt.Sprint(HiWhite("Account #"),HiWhite(strconv.Itoa(acc.ID))," - ", HiYellow(acc.AccountName))) 72 table := NewTable(cli, os.Stdout) 73 table.SetHeader([]string{"App ID", "App Name"}) 74 table.SetColumnColor(tablewriter.Colors{tablewriter.Normal,tablewriter.FgWhiteColor}, 75 tablewriter.Colors{tablewriter.Normal, tablewriter.FgHiGreenColor}) 76 table.SetAlignment(tablewriter.ALIGN_LEFT) 77 table.SetCenterSeparator("") 78 table.SetColumnSeparator("") 79 table.SetNoWhiteSpace(true) 80 table.SetAutoMergeCells(true) 81 table.SetRowLine(true) 82 for _, app := range acc.Applications { 83 r := []string{strconv.Itoa(app.ID), strings.Trim(app.ApplicationName,"\"")} 84 table.Append(r) 85 } 86 table.Render() 87 fmt.Println() 88 fmt.Println() 89 } 90 return err 91 } 92 93 // AppsInfoCmd shows detailed information on an app running on Section 94 type AppsInfoCmd struct { 95 AccountID int `required short:"a"` 96 AppID int `required short:"i"` 97 } 98 99 // Run executes the command 100 func (c *AppsInfoCmd) Run(cli *CLI, logWriters *LogWriters) (err error) { 101 s := NewSpinner("Looking up app info", logWriters) 102 s.Start() 103 104 app, err := api.Application(c.AccountID, c.AppID) 105 s.Stop() 106 fmt.Println() 107 if err != nil { 108 return err 109 } 110 111 if !(cli.Quiet){ 112 fmt.Printf("🌎🌏🌍\n") 113 fmt.Printf("App Name: %s\n", app.ApplicationName) 114 fmt.Printf("App ID: %d\n", app.ID) 115 fmt.Printf("Environment count: %d\n", len(app.Environments)) 116 117 for i, env := range app.Environments { 118 fmt.Printf("\n-----------------\n\n") 119 fmt.Printf("Environment #%d: %s (ID:%d)\n\n", i+1, env.EnvironmentName, env.ID) 120 fmt.Printf("💬 Domains (%d total)\n", len(env.Domains)) 121 122 for _, dom := range env.Domains { 123 fmt.Println() 124 125 table := NewTable(cli, os.Stdout) 126 table.SetHeader([]string{"Attribute", "Value"}) 127 table.SetHeaderColor(tablewriter.Colors{tablewriter.Normal,tablewriter.FgWhiteColor}, 128 tablewriter.Colors{tablewriter.Normal, tablewriter.FgWhiteColor}) 129 table.SetColumnColor(tablewriter.Colors{tablewriter.Normal,tablewriter.FgWhiteColor}, 130 tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiCyanColor}) 131 table.SetAutoMergeCells(true) 132 r := [][]string{ 133 {"Domain name", dom.Name}, 134 {"Zone name", dom.ZoneName}, 135 {"CNAME", dom.CNAME}, 136 {"Mode", dom.Mode}, 137 } 138 table.AppendBulk(r) 139 table.Render() 140 } 141 142 fmt.Println() 143 mod := "modules" 144 if len(env.Stack) == 1 { 145 mod = "module" 146 } 147 fmt.Printf("🥞 Stack (%d %s total)\n", len(env.Stack), mod) 148 fmt.Println() 149 150 table := NewTable(cli, os.Stdout) 151 table.SetHeader([]string{"Name", "Image"}) 152 table.SetHeaderColor(tablewriter.Colors{tablewriter.Normal,tablewriter.FgWhiteColor}, 153 tablewriter.Colors{tablewriter.Normal, tablewriter.FgWhiteColor}) 154 table.SetColumnColor(tablewriter.Colors{tablewriter.Normal,tablewriter.FgWhiteColor}, 155 tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiCyanColor}) 156 table.SetAutoMergeCells(true) 157 for _, p := range env.Stack { 158 r := []string{p.Name, p.Image} 159 table.Append(r) 160 } 161 table.Render() 162 } 163 164 fmt.Println() 165 } 166 167 168 return err 169 } 170 171 // AppsCreateCmd handles creating apps on Section 172 type AppsCreateCmd struct { 173 AccountID int `required short:"a" help:"ID of account to create the app under"` 174 Hostname string `required short:"d" help:"FQDN the app can be accessed at"` 175 Origin string `required short:"g" help:"URL to fetch the origin"` 176 StackName string `required short:"s" help:"Name of stack to deploy. Try, for example, nodejs-basic"` 177 } 178 179 // Run executes the command 180 func (c *AppsCreateCmd) Run(logWriters *LogWriters) (err error) { 181 s := NewSpinner(fmt.Sprintf("Creating new app %s", c.Hostname),logWriters) 182 s.Start() 183 184 api.Timeout = 120 * time.Second // this specific request can take a long time 185 r, err := api.ApplicationCreate(c.AccountID, c.Hostname, c.Origin, c.StackName) 186 s.Stop() 187 fmt.Println() 188 if err != nil { 189 if err == api.ErrStatusForbidden { 190 stacks, herr := api.Stacks() 191 if herr != nil { 192 return fmt.Errorf("unable to query stacks: %w", herr) 193 } 194 for _, s := range stacks { 195 if s.Name == c.StackName { 196 return err 197 } 198 } 199 return fmt.Errorf("bad request: unable to find stack %s", c.StackName) 200 } 201 return err 202 } 203 204 log.Info().Msg(fmt.Sprintf("\nSuccess: created app '%s' with id '%d'\n", r.ApplicationName, r.ID)) 205 206 return err 207 } 208 209 // AppsDeleteCmd handles deleting apps on Section 210 type AppsDeleteCmd struct { 211 AccountID int `required short:"a" help:"ID of account the app belongs to"` 212 AppID int `required short:"i" help:"ID of the app to delete"` 213 } 214 215 // Run executes the command 216 func (c *AppsDeleteCmd) Run(logWriters *LogWriters) (err error) { 217 s := NewSpinner(fmt.Sprintf("Deleting app with id '%d'", c.AppID),logWriters) 218 s.Start() 219 220 api.Timeout = 120 * time.Second // this specific request can take a long time 221 _, err = api.ApplicationDelete(c.AccountID, c.AppID) 222 s.Stop() 223 if err != nil { 224 return err 225 } 226 227 log.Info().Msg(fmt.Sprintf("\nSuccess: deleted app with id '%d'\n", c.AppID)) 228 229 return err 230 } 231 232 // AppsStacksCmd lists available stacks to create new apps with 233 type AppsStacksCmd struct{} 234 235 // Run executes the command 236 func (c *AppsStacksCmd) Run(cli *CLI, logWriters *LogWriters) (err error) { 237 s := NewSpinner("Looking up stacks",logWriters) 238 s.Start() 239 k, err := api.Stacks() 240 s.Stop() 241 if err != nil { 242 return fmt.Errorf("unable to look up stacks: %w", err) 243 } 244 245 table := NewTable(cli, os.Stdout) 246 table.SetHeader([]string{"Name", "Label", "Description", "Type"}) 247 248 for _, s := range k { 249 r := []string{s.Name, s.Label, s.Description, s.Type} 250 table.Append(r) 251 } 252 253 table.Render() 254 return err 255 }