github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/overlord/hookstate/ctlcmd/is_connected.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 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 ctlcmd
    21  
    22  import (
    23  	"fmt"
    24  
    25  	"github.com/snapcore/snapd/i18n"
    26  	"github.com/snapcore/snapd/interfaces"
    27  	"github.com/snapcore/snapd/overlord/ifacestate"
    28  	"github.com/snapcore/snapd/overlord/snapstate"
    29  )
    30  
    31  type isConnectedCommand struct {
    32  	baseCommand
    33  
    34  	Positional struct {
    35  		PlugOrSlotSpec string `positional-args:"true" positional-arg-name:"<plug|slot>"`
    36  	} `positional-args:"true" required:"true"`
    37  }
    38  
    39  var shortIsConnectedHelp = i18n.G(`Return success if the given plug or slot is connected, and failure otherwise`)
    40  var longIsConnectedHelp = i18n.G(`
    41  The is-connected command returns success if the given plug or slot of the
    42  calling snap is connected, and failure otherwise.
    43  
    44  $ snapctl is-connected plug
    45  $ echo $?
    46  1
    47  
    48  Snaps can only query their own plugs and slots - snap name is implicit and
    49  implied by the snapctl execution context.
    50  `)
    51  
    52  func init() {
    53  	addCommand("is-connected", shortIsConnectedHelp, longIsConnectedHelp, func() command {
    54  		return &isConnectedCommand{}
    55  	})
    56  }
    57  
    58  func (c *isConnectedCommand) Execute(args []string) error {
    59  	plugOrSlot := c.Positional.PlugOrSlotSpec
    60  
    61  	context := c.context()
    62  	if context == nil {
    63  		return fmt.Errorf("cannot check connection status without a context")
    64  	}
    65  
    66  	snapName := context.InstanceName()
    67  
    68  	st := context.State()
    69  	st.Lock()
    70  	defer st.Unlock()
    71  
    72  	info, err := snapstate.CurrentInfo(st, snapName)
    73  	if err != nil {
    74  		return fmt.Errorf("internal error: cannot get snap info: %s", err)
    75  	}
    76  
    77  	// XXX: This will fail for implicit slots.  In practice, this
    78  	// would only affect calls that used the "core" snap as
    79  	// context.  That snap does not have any hooks using
    80  	// is-connected, so the limitation is probably moot.
    81  	if info.Plugs[plugOrSlot] == nil && info.Slots[plugOrSlot] == nil {
    82  		return fmt.Errorf("snap %q has no plug or slot named %q", snapName, plugOrSlot)
    83  	}
    84  
    85  	conns, err := ifacestate.ConnectionStates(st)
    86  	if err != nil {
    87  		return fmt.Errorf("internal error: cannot get connections: %s", err)
    88  	}
    89  
    90  	// snapName is the name of the snap executing snapctl command, it's
    91  	// obtained from the context (ephemeral if run by apps, or full if run by
    92  	// hooks). plug and slot names are unique within a snap, so there is no
    93  	// ambiguity when matching.
    94  	for refStr, connState := range conns {
    95  		if connState.Undesired || connState.HotplugGone {
    96  			continue
    97  		}
    98  		connRef, err := interfaces.ParseConnRef(refStr)
    99  		if err != nil {
   100  			return fmt.Errorf("internal error: %s", err)
   101  		}
   102  
   103  		matchingPlug := connRef.PlugRef.Snap == snapName && connRef.PlugRef.Name == plugOrSlot
   104  		matchingSlot := connRef.SlotRef.Snap == snapName && connRef.SlotRef.Name == plugOrSlot
   105  		if matchingPlug || matchingSlot {
   106  			return nil
   107  		}
   108  	}
   109  
   110  	return &UnsuccessfulError{ExitCode: 1}
   111  }