github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_services.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016-2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main 21 22 import ( 23 "fmt" 24 "strconv" 25 26 "github.com/jessevdk/go-flags" 27 28 "github.com/snapcore/snapd/client" 29 "github.com/snapcore/snapd/client/clientutil" 30 "github.com/snapcore/snapd/i18n" 31 "github.com/snapcore/snapd/snap" 32 ) 33 34 type svcStatus struct { 35 clientMixin 36 Positional struct { 37 ServiceNames []serviceName 38 } `positional-args:"yes"` 39 } 40 41 type svcLogs struct { 42 clientMixin 43 N string `short:"n" default:"10"` 44 Follow bool `short:"f"` 45 Positional struct { 46 ServiceNames []serviceName `required:"1"` 47 } `positional-args:"yes" required:"yes"` 48 } 49 50 var ( 51 shortServicesHelp = i18n.G("Query the status of services") 52 longServicesHelp = i18n.G(` 53 The services command lists information about the services specified, or about 54 the services in all currently installed snaps. 55 `) 56 shortLogsHelp = i18n.G("Retrieve logs for services") 57 longLogsHelp = i18n.G(` 58 The logs command fetches logs of the given services and displays them in 59 chronological order. 60 `) 61 shortStartHelp = i18n.G("Start services") 62 longStartHelp = i18n.G(` 63 The start command starts, and optionally enables, the given services. 64 `) 65 shortStopHelp = i18n.G("Stop services") 66 longStopHelp = i18n.G(` 67 The stop command stops, and optionally disables, the given services. 68 `) 69 shortRestartHelp = i18n.G("Restart services") 70 longRestartHelp = i18n.G(` 71 The restart command restarts the given services. 72 73 If the --reload option is given, for each service whose app has a reload 74 command, a reload is performed instead of a restart. 75 `) 76 ) 77 78 func init() { 79 argdescs := []argDesc{{ 80 // TRANSLATORS: This needs to begin with < and end with > 81 name: i18n.G("<service>"), 82 // TRANSLATORS: This should not start with a lowercase letter. 83 desc: i18n.G("A service specification, which can be just a snap name (for all services in the snap), or <snap>.<app> for a single service."), 84 }} 85 addCommand("services", shortServicesHelp, longServicesHelp, func() flags.Commander { return &svcStatus{} }, nil, argdescs) 86 addCommand("logs", shortLogsHelp, longLogsHelp, func() flags.Commander { return &svcLogs{} }, 87 map[string]string{ 88 // TRANSLATORS: This should not start with a lowercase letter. 89 "n": i18n.G("Show only the given number of lines, or 'all'."), 90 // TRANSLATORS: This should not start with a lowercase letter. 91 "f": i18n.G("Wait for new lines and print them as they come in."), 92 }, argdescs) 93 94 addCommand("start", shortStartHelp, longStartHelp, func() flags.Commander { return &svcStart{} }, 95 waitDescs.also(map[string]string{ 96 // TRANSLATORS: This should not start with a lowercase letter. 97 "enable": i18n.G("As well as starting the service now, arrange for it to be started on boot."), 98 }), argdescs) 99 addCommand("stop", shortStopHelp, longStopHelp, func() flags.Commander { return &svcStop{} }, 100 waitDescs.also(map[string]string{ 101 // TRANSLATORS: This should not start with a lowercase letter. 102 "disable": i18n.G("As well as stopping the service now, arrange for it to no longer be started on boot."), 103 }), argdescs) 104 addCommand("restart", shortRestartHelp, longRestartHelp, func() flags.Commander { return &svcRestart{} }, 105 waitDescs.also(map[string]string{ 106 // TRANSLATORS: This should not start with a lowercase letter. 107 "reload": i18n.G("If the service has a reload command, use it instead of restarting."), 108 }), argdescs) 109 } 110 111 func svcNames(s []serviceName) []string { 112 svcNames := make([]string, len(s)) 113 for i, svcName := range s { 114 svcNames[i] = string(svcName) 115 } 116 return svcNames 117 } 118 119 func (s *svcStatus) Execute(args []string) error { 120 if len(args) > 0 { 121 return ErrExtraArgs 122 } 123 124 services, err := s.client.Apps(svcNames(s.Positional.ServiceNames), client.AppOptions{Service: true}) 125 if err != nil { 126 return err 127 } 128 129 if len(services) == 0 { 130 fmt.Fprintln(Stderr, i18n.G("There are no services provided by installed snaps.")) 131 return nil 132 } 133 134 w := tabWriter() 135 defer w.Flush() 136 137 fmt.Fprintln(w, i18n.G("Service\tStartup\tCurrent\tNotes")) 138 139 for _, svc := range services { 140 startup := i18n.G("disabled") 141 if svc.Enabled { 142 startup = i18n.G("enabled") 143 } 144 current := i18n.G("inactive") 145 if svc.DaemonScope == snap.UserDaemon { 146 current = "-" 147 } else if svc.Active { 148 current = i18n.G("active") 149 } 150 fmt.Fprintf(w, "%s.%s\t%s\t%s\t%s\n", svc.Snap, svc.Name, startup, current, clientutil.ClientAppInfoNotes(svc)) 151 } 152 153 return nil 154 } 155 156 func (s *svcLogs) Execute(args []string) error { 157 if len(args) > 0 { 158 return ErrExtraArgs 159 } 160 161 sN := -1 162 if s.N != "all" { 163 n, err := strconv.ParseInt(s.N, 0, 32) 164 if n < 0 || err != nil { 165 return fmt.Errorf(i18n.G("invalid argument for flag ‘-n’: expected a non-negative integer argument, or “all”.")) 166 } 167 sN = int(n) 168 } 169 170 logs, err := s.client.Logs(svcNames(s.Positional.ServiceNames), client.LogOptions{N: sN, Follow: s.Follow}) 171 if err != nil { 172 return err 173 } 174 175 for log := range logs { 176 fmt.Fprintln(Stdout, log) 177 } 178 179 return nil 180 } 181 182 type svcStart struct { 183 waitMixin 184 Positional struct { 185 ServiceNames []serviceName `required:"1"` 186 } `positional-args:"yes" required:"yes"` 187 Enable bool `long:"enable"` 188 } 189 190 func (s *svcStart) Execute(args []string) error { 191 if len(args) > 0 { 192 return ErrExtraArgs 193 } 194 names := svcNames(s.Positional.ServiceNames) 195 changeID, err := s.client.Start(names, client.StartOptions{Enable: s.Enable}) 196 if err != nil { 197 return err 198 } 199 if _, err := s.wait(changeID); err != nil { 200 if err == noWait { 201 return nil 202 } 203 return err 204 } 205 206 fmt.Fprintf(Stdout, i18n.G("Started.\n")) 207 208 return nil 209 } 210 211 type svcStop struct { 212 waitMixin 213 Positional struct { 214 ServiceNames []serviceName `required:"1"` 215 } `positional-args:"yes" required:"yes"` 216 Disable bool `long:"disable"` 217 } 218 219 func (s *svcStop) Execute(args []string) error { 220 if len(args) > 0 { 221 return ErrExtraArgs 222 } 223 names := svcNames(s.Positional.ServiceNames) 224 changeID, err := s.client.Stop(names, client.StopOptions{Disable: s.Disable}) 225 if err != nil { 226 return err 227 } 228 if _, err := s.wait(changeID); err != nil { 229 if err == noWait { 230 return nil 231 } 232 return err 233 } 234 235 fmt.Fprintf(Stdout, i18n.G("Stopped.\n")) 236 237 return nil 238 } 239 240 type svcRestart struct { 241 waitMixin 242 Positional struct { 243 ServiceNames []serviceName `required:"1"` 244 } `positional-args:"yes" required:"yes"` 245 Reload bool `long:"reload"` 246 } 247 248 func (s *svcRestart) Execute(args []string) error { 249 if len(args) > 0 { 250 return ErrExtraArgs 251 } 252 names := svcNames(s.Positional.ServiceNames) 253 changeID, err := s.client.Restart(names, client.RestartOptions{Reload: s.Reload}) 254 if err != nil { 255 return err 256 } 257 if _, err := s.wait(changeID); err != nil { 258 if err == noWait { 259 return nil 260 } 261 return err 262 } 263 264 fmt.Fprintf(Stdout, i18n.G("Restarted.\n")) 265 266 return nil 267 }