File: system_key.go

package info (click to toggle)
snapd 2.71-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 79,536 kB
  • sloc: ansic: 16,114; sh: 16,105; python: 9,941; makefile: 1,890; exp: 190; awk: 40; xml: 22
file content (136 lines) | stat: -rw-r--r-- 3,802 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2025 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package client

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"time"
)

type SystemKeyMismatchAction string

const (
	MismatchActionProceed       SystemKeyMismatchAction = "proceed"
	MismatchActionWaitForChange SystemKeyMismatchAction = "wait-for-change"
)

var (
	// ErrMismatchAdviceUnsupported indicates that current snapd does not
	// support the API to obtain advice on system key mismatch.
	ErrMismatchAdviceUnsupported = errors.New("system-key mismatch action is not supported")
)

type AdvisedAction struct {
	SuggestedAction SystemKeyMismatchAction
	// ChangeID carries the change to wait for if action was "wait-for-change".
	ChangeID string
}

// SystemKeyMismatchAdvise is usually called after having detected a mismatch of
// the system keys and performs an access to snapd API with the goal of getting
// an advice from snapd on how to proceed. The request carries system-key
// derived by the client. Returns an error of type *Error for errors
// communicated directly through snapd API. Note, that the response is only
// advisory.
func (client *Client) SystemKeyMismatchAdvice(systemKey any) (*AdvisedAction, error) {
	// system key implements Stringer
	sks, ok := systemKey.(fmt.Stringer)
	if systemKey == nil || !ok {
		return nil, fmt.Errorf("cannot be marshaled as system key")
	}

	sk := sks.String()
	if sk == "" {
		return nil, fmt.Errorf("no system key provided")
	}

	// same as for /v2/system-info
	opts := &doOptions{
		Timeout: 25 * time.Second,
		Retry:   doRetry,
	}

	// update maintenance status
	client.checkMaintenanceJSON()

	var rsp response
	var body io.Reader

	d, doErr := json.Marshal(struct {
		Action    string `json:"action"`
		SystemKey string `json:"system-key"`
	}{
		Action:    "advise-system-key-mismatch",
		SystemKey: sk,
	})
	if doErr != nil {
		return nil, fmt.Errorf("cannot marshal request data: %w", doErr)
	}

	body = bytes.NewReader(d)
	hdrs := map[string]string{
		"Content-Type": "application/json",
	}
	statusCode, doErr := client.do("POST", "/v2/system-info", nil, hdrs, body, &rsp, opts)
	if statusCode == 405 {
		// Method Not Allowed, most likely we are talking to an older version of
		// snapd, which does not support the advisory system key mismatch
		// handling, but since we got a response, snapd must be up already
		return nil, ErrMismatchAdviceUnsupported
	}

	if doErr != nil {
		return nil, doErr
	}

	if err := rsp.err(client, statusCode); err != nil {
		return nil, err
	}

	var act *AdvisedAction

	switch rsp.Type {
	case "sync":
		// body in the request?
		act = &AdvisedAction{
			SuggestedAction: MismatchActionProceed,
		}
	case "async":
		// async response with change implies that the client should wait
		if rsp.Change == "" {
			return nil, fmt.Errorf("async response without change reference")
		}
		act = &AdvisedAction{
			SuggestedAction: MismatchActionWaitForChange,
			ChangeID:        rsp.Change,
		}
	default:
		return nil, fmt.Errorf(`unexpected response for "POST" on "/v2/system-info", got %q`, rsp.Type)
	}

	client.warningCount = rsp.WarningCount
	client.warningTimestamp = rsp.WarningTimestamp

	return act, nil
}