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  }