github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/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 timeMixin 44 N string `short:"n" default:"10"` 45 Follow bool `short:"f"` 46 Positional struct { 47 ServiceNames []serviceName `required:"1"` 48 } `positional-args:"yes" required:"yes"` 49 } 50 51 var ( 52 shortServicesHelp = i18n.G("Query the status of services") 53 longServicesHelp = i18n.G(` 54 The services command lists information about the services specified, or about 55 the services in all currently installed snaps. 56 `) 57 shortLogsHelp = i18n.G("Retrieve logs for services") 58 longLogsHelp = i18n.G(` 59 The logs command fetches logs of the given services and displays them in 60 chronological order. 61 `) 62 shortStartHelp = i18n.G("Start services") 63 longStartHelp = i18n.G(` 64 The start command starts, and optionally enables, the given services. 65 `) 66 shortStopHelp = i18n.G("Stop services") 67 longStopHelp = i18n.G(` 68 The stop command stops, and optionally disables, the given services. 69 `) 70 shortRestartHelp = i18n.G("Restart services") 71 longRestartHelp = i18n.G(` 72 The restart command restarts the given services. 73 74 If the --reload option is given, for each service whose app has a reload 75 command, a reload is performed instead of a restart. 76 `) 77 ) 78 79 func init() { 80 argdescs := []argDesc{{ 81 // TRANSLATORS: This needs to begin with < and end with > 82 name: i18n.G("<service>"), 83 // TRANSLATORS: This should not start with a lowercase letter. 84 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."), 85 }} 86 addCommand("services", shortServicesHelp, longServicesHelp, func() flags.Commander { return &svcStatus{} }, nil, argdescs) 87 addCommand("logs", shortLogsHelp, longLogsHelp, func() flags.Commander { return &svcLogs{} }, 88 timeDescs.also(map[string]string{ 89 // TRANSLATORS: This should not start with a lowercase letter. 90 "n": i18n.G("Show only the given number of lines, or 'all'."), 91 // TRANSLATORS: This should not start with a lowercase letter. 92 "f": i18n.G("Wait for new lines and print them as they come in."), 93 }), argdescs) 94 95 addCommand("start", shortStartHelp, longStartHelp, func() flags.Commander { return &svcStart{} }, 96 waitDescs.also(map[string]string{ 97 // TRANSLATORS: This should not start with a lowercase letter. 98 "enable": i18n.G("As well as starting the service now, arrange for it to be started on boot."), 99 }), argdescs) 100 addCommand("stop", shortStopHelp, longStopHelp, func() flags.Commander { return &svcStop{} }, 101 waitDescs.also(map[string]string{ 102 // TRANSLATORS: This should not start with a lowercase letter. 103 "disable": i18n.G("As well as stopping the service now, arrange for it to no longer be started on boot."), 104 }), argdescs) 105 addCommand("restart", shortRestartHelp, longRestartHelp, func() flags.Commander { return &svcRestart{} }, 106 waitDescs.also(map[string]string{ 107 // TRANSLATORS: This should not start with a lowercase letter. 108 "reload": i18n.G("If the service has a reload command, use it instead of restarting."), 109 }), argdescs) 110 } 111 112 func svcNames(s []serviceName) []string { 113 svcNames := make([]string, len(s)) 114 for i, svcName := range s { 115 svcNames[i] = string(svcName) 116 } 117 return svcNames 118 } 119 120 func (s *svcStatus) Execute(args []string) error { 121 if len(args) > 0 { 122 return ErrExtraArgs 123 } 124 125 services, err := s.client.Apps(svcNames(s.Positional.ServiceNames), client.AppOptions{Service: true}) 126 if err != nil { 127 return err 128 } 129 130 if len(services) == 0 { 131 fmt.Fprintln(Stderr, i18n.G("There are no services provided by installed snaps.")) 132 return nil 133 } 134 135 w := tabWriter() 136 defer w.Flush() 137 138 fmt.Fprintln(w, i18n.G("Service\tStartup\tCurrent\tNotes")) 139 140 for _, svc := range services { 141 startup := i18n.G("disabled") 142 if svc.Enabled { 143 startup = i18n.G("enabled") 144 } 145 current := i18n.G("inactive") 146 if svc.DaemonScope == snap.UserDaemon { 147 current = "-" 148 } else if svc.Active { 149 current = i18n.G("active") 150 } 151 fmt.Fprintf(w, "%s.%s\t%s\t%s\t%s\n", svc.Snap, svc.Name, startup, current, clientutil.ClientAppInfoNotes(svc)) 152 } 153 154 return nil 155 } 156 157 func (s *svcLogs) Execute(args []string) error { 158 if len(args) > 0 { 159 return ErrExtraArgs 160 } 161 162 sN := -1 163 if s.N != "all" { 164 n, err := strconv.ParseInt(s.N, 0, 32) 165 if n < 0 || err != nil { 166 return fmt.Errorf(i18n.G("invalid argument for flag ‘-n’: expected a non-negative integer argument, or “all”.")) 167 } 168 sN = int(n) 169 } 170 171 logs, err := s.client.Logs(svcNames(s.Positional.ServiceNames), client.LogOptions{N: sN, Follow: s.Follow}) 172 if err != nil { 173 return err 174 } 175 176 for log := range logs { 177 if s.AbsTime { 178 fmt.Fprintln(Stdout, log.StringInUTC()) 179 } else { 180 fmt.Fprintln(Stdout, log) 181 } 182 } 183 184 return nil 185 } 186 187 type svcStart struct { 188 waitMixin 189 Positional struct { 190 ServiceNames []serviceName `required:"1"` 191 } `positional-args:"yes" required:"yes"` 192 Enable bool `long:"enable"` 193 } 194 195 func (s *svcStart) Execute(args []string) error { 196 if len(args) > 0 { 197 return ErrExtraArgs 198 } 199 names := svcNames(s.Positional.ServiceNames) 200 changeID, err := s.client.Start(names, client.StartOptions{Enable: s.Enable}) 201 if err != nil { 202 return err 203 } 204 if _, err := s.wait(changeID); err != nil { 205 if err == noWait { 206 return nil 207 } 208 return err 209 } 210 211 fmt.Fprintf(Stdout, i18n.G("Started.\n")) 212 213 return nil 214 } 215 216 type svcStop struct { 217 waitMixin 218 Positional struct { 219 ServiceNames []serviceName `required:"1"` 220 } `positional-args:"yes" required:"yes"` 221 Disable bool `long:"disable"` 222 } 223 224 func (s *svcStop) Execute(args []string) error { 225 if len(args) > 0 { 226 return ErrExtraArgs 227 } 228 names := svcNames(s.Positional.ServiceNames) 229 changeID, err := s.client.Stop(names, client.StopOptions{Disable: s.Disable}) 230 if err != nil { 231 return err 232 } 233 if _, err := s.wait(changeID); err != nil { 234 if err == noWait { 235 return nil 236 } 237 return err 238 } 239 240 fmt.Fprintf(Stdout, i18n.G("Stopped.\n")) 241 242 return nil 243 } 244 245 type svcRestart struct { 246 waitMixin 247 Positional struct { 248 ServiceNames []serviceName `required:"1"` 249 } `positional-args:"yes" required:"yes"` 250 Reload bool `long:"reload"` 251 } 252 253 func (s *svcRestart) Execute(args []string) error { 254 if len(args) > 0 { 255 return ErrExtraArgs 256 } 257 names := svcNames(s.Positional.ServiceNames) 258 changeID, err := s.client.Restart(names, client.RestartOptions{Reload: s.Reload}) 259 if err != nil { 260 return err 261 } 262 if _, err := s.wait(changeID); err != nil { 263 if err == noWait { 264 return nil 265 } 266 return err 267 } 268 269 fmt.Fprintf(Stdout, i18n.G("Restarted.\n")) 270 271 return nil 272 }