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  }