github.com/freetocompute/snapd@v0.0.0-20210618182524-2fb355d72fd9/client/systems.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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 client
    21  
    22  import (
    23  	"bytes"
    24  	"encoding/json"
    25  	"fmt"
    26  
    27  	"golang.org/x/xerrors"
    28  
    29  	"github.com/snapcore/snapd/snap"
    30  )
    31  
    32  // SystemModelData contains information about the model
    33  type SystemModelData struct {
    34  	// Model as the model assertion
    35  	Model string `json:"model,omitempty"`
    36  	// BrandID corresponds to brand-id in the model assertion
    37  	BrandID string `json:"brand-id,omitempty"`
    38  	// DisplayName is human friendly name, corresponds to display-name in
    39  	// the model assertion
    40  	DisplayName string `json:"display-name,omitempty"`
    41  }
    42  
    43  type System struct {
    44  	// Current is true when the system running now was installed from that
    45  	// recovery seed
    46  	Current bool `json:"current,omitempty"`
    47  	// Label of the recovery system
    48  	Label string `json:"label,omitempty"`
    49  	// Model information
    50  	Model SystemModelData `json:"model,omitempty"`
    51  	// Brand information
    52  	Brand snap.StoreAccount `json:"brand,omitempty"`
    53  	// Actions available for this system
    54  	Actions []SystemAction `json:"actions,omitempty"`
    55  }
    56  
    57  type SystemAction struct {
    58  	// Title is a user presentable action description
    59  	Title string `json:"title,omitempty"`
    60  	// Mode given action can be executed in
    61  	Mode string `json:"mode,omitempty"`
    62  }
    63  
    64  // ListSystems list all systems available for seeding or recovery.
    65  func (client *Client) ListSystems() ([]System, error) {
    66  	type systemsResponse struct {
    67  		Systems []System `json:"systems,omitempty"`
    68  	}
    69  
    70  	var rsp systemsResponse
    71  
    72  	if _, err := client.doSync("GET", "/v2/systems", nil, nil, nil, &rsp); err != nil {
    73  		return nil, xerrors.Errorf("cannot list recovery systems: %v", err)
    74  	}
    75  	return rsp.Systems, nil
    76  }
    77  
    78  // DoSystemAction issues a request to perform an action using the given seed
    79  // system and its mode.
    80  func (client *Client) DoSystemAction(systemLabel string, action *SystemAction) error {
    81  	if systemLabel == "" {
    82  		return fmt.Errorf("cannot request an action without the system")
    83  	}
    84  	if action == nil {
    85  		return fmt.Errorf("cannot request an action without one")
    86  	}
    87  	// deeper verification is done by the backend
    88  
    89  	req := struct {
    90  		Action string `json:"action"`
    91  		*SystemAction
    92  	}{
    93  		Action:       "do",
    94  		SystemAction: action,
    95  	}
    96  
    97  	var body bytes.Buffer
    98  	if err := json.NewEncoder(&body).Encode(&req); err != nil {
    99  		return err
   100  	}
   101  	if _, err := client.doSync("POST", "/v2/systems/"+systemLabel, nil, nil, &body, nil); err != nil {
   102  		return xerrors.Errorf("cannot request system action: %v", err)
   103  	}
   104  	return nil
   105  }
   106  
   107  // RebootToSystem issues a request to reboot into system with the
   108  // given label and the given mode.
   109  //
   110  // When called without a systemLabel and without a mode it will just
   111  // trigger a regular reboot.
   112  //
   113  // When called without a systemLabel but with a mode it will use
   114  // the current system to enter the given mode.
   115  //
   116  // Note that "recover" and "run" modes are only available for the
   117  // current system.
   118  func (client *Client) RebootToSystem(systemLabel, mode string) error {
   119  	// verification is done by the backend
   120  
   121  	req := struct {
   122  		Action string `json:"action"`
   123  		Mode   string `json:"mode"`
   124  	}{
   125  		Action: "reboot",
   126  		Mode:   mode,
   127  	}
   128  
   129  	var body bytes.Buffer
   130  	if err := json.NewEncoder(&body).Encode(&req); err != nil {
   131  		return err
   132  	}
   133  	if _, err := client.doSync("POST", "/v2/systems/"+systemLabel, nil, nil, &body, nil); err != nil {
   134  		if systemLabel != "" {
   135  			return xerrors.Errorf("cannot request system reboot into %q: %v", systemLabel, err)
   136  		}
   137  		return xerrors.Errorf("cannot request system reboot: %v", err)
   138  	}
   139  	return nil
   140  }