github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/client/change.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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  	"net/url"
    27  	"time"
    28  )
    29  
    30  // A Change is a modification to the system state.
    31  type Change struct {
    32  	ID      string  `json:"id"`
    33  	Kind    string  `json:"kind"`
    34  	Summary string  `json:"summary"`
    35  	Status  string  `json:"status"`
    36  	Tasks   []*Task `json:"tasks,omitempty"`
    37  	Ready   bool    `json:"ready"`
    38  	Err     string  `json:"err,omitempty"`
    39  
    40  	SpawnTime time.Time `json:"spawn-time,omitempty"`
    41  	ReadyTime time.Time `json:"ready-time,omitempty"`
    42  
    43  	data map[string]*json.RawMessage
    44  }
    45  
    46  var ErrNoData = fmt.Errorf("data entry not found")
    47  
    48  // Get unmarshals into value the kind-specific data with the provided key.
    49  func (c *Change) Get(key string, value interface{}) error {
    50  	raw := c.data[key]
    51  	if raw == nil {
    52  		return ErrNoData
    53  	}
    54  	return json.Unmarshal([]byte(*raw), value)
    55  }
    56  
    57  // A Task is an operation done to change the system's state.
    58  type Task struct {
    59  	ID       string       `json:"id"`
    60  	Kind     string       `json:"kind"`
    61  	Summary  string       `json:"summary"`
    62  	Status   string       `json:"status"`
    63  	Log      []string     `json:"log,omitempty"`
    64  	Progress TaskProgress `json:"progress"`
    65  
    66  	SpawnTime time.Time `json:"spawn-time,omitempty"`
    67  	ReadyTime time.Time `json:"ready-time,omitempty"`
    68  }
    69  
    70  type TaskProgress struct {
    71  	Label string `json:"label"`
    72  	Done  int    `json:"done"`
    73  	Total int    `json:"total"`
    74  }
    75  
    76  type changeAndData struct {
    77  	Change
    78  	Data map[string]*json.RawMessage `json:"data"`
    79  }
    80  
    81  // Change fetches information about a Change given its ID.
    82  func (client *Client) Change(id string) (*Change, error) {
    83  	var chgd changeAndData
    84  	_, err := client.doSync("GET", "/v2/changes/"+id, nil, nil, nil, &chgd)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	chgd.Change.data = chgd.Data
    90  	return &chgd.Change, nil
    91  }
    92  
    93  // Abort attempts to abort a change that is in not yet ready.
    94  func (client *Client) Abort(id string) (*Change, error) {
    95  	var postData struct {
    96  		Action string `json:"action"`
    97  	}
    98  	postData.Action = "abort"
    99  
   100  	var body bytes.Buffer
   101  	if err := json.NewEncoder(&body).Encode(postData); err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	var chg Change
   106  	if _, err := client.doSync("POST", "/v2/changes/"+id, nil, nil, &body, &chg); err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	return &chg, nil
   111  }
   112  
   113  type ChangeSelector uint8
   114  
   115  func (c ChangeSelector) String() string {
   116  	switch c {
   117  	case ChangesInProgress:
   118  		return "in-progress"
   119  	case ChangesReady:
   120  		return "ready"
   121  	case ChangesAll:
   122  		return "all"
   123  	}
   124  
   125  	panic(fmt.Sprintf("unknown ChangeSelector %d", c))
   126  }
   127  
   128  const (
   129  	ChangesInProgress ChangeSelector = 1 << iota
   130  	ChangesReady
   131  	ChangesAll = ChangesReady | ChangesInProgress
   132  )
   133  
   134  type ChangesOptions struct {
   135  	SnapName string // if empty, no filtering by name is done
   136  	Selector ChangeSelector
   137  }
   138  
   139  func (client *Client) Changes(opts *ChangesOptions) ([]*Change, error) {
   140  	query := url.Values{}
   141  	if opts != nil {
   142  		if opts.Selector != 0 {
   143  			query.Set("select", opts.Selector.String())
   144  		}
   145  		if opts.SnapName != "" {
   146  			query.Set("for", opts.SnapName)
   147  		}
   148  	}
   149  
   150  	var chgds []changeAndData
   151  	_, err := client.doSync("GET", "/v2/changes", query, nil, nil, &chgds)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	var chgs []*Change
   157  	for i := range chgds {
   158  		chgd := &chgds[i]
   159  		chgd.Change.data = chgd.Data
   160  		chgs = append(chgs, &chgd.Change)
   161  	}
   162  
   163  	return chgs, err
   164  }