github.com/chipaca/snappy@v0.0.0-20210104084008-1f06296fe8ad/client/snapshot.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 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 "context" 25 "encoding/json" 26 "errors" 27 "fmt" 28 "io" 29 "net/url" 30 "strconv" 31 "strings" 32 "time" 33 34 "github.com/snapcore/snapd/snap" 35 ) 36 37 // SnapshotExportMediaType is the media type used to identify snapshot exports in the API. 38 const SnapshotExportMediaType = "application/x.snapd.snapshot" 39 40 var ( 41 ErrSnapshotSetNotFound = errors.New("no snapshot set with the given ID") 42 ErrSnapshotSnapsNotFound = errors.New("no snapshot for the requested snaps found in the set with the given ID") 43 ) 44 45 // A snapshotAction is used to request an operation on a snapshot. 46 type snapshotAction struct { 47 SetID uint64 `json:"set"` 48 Action string `json:"action"` 49 Snaps []string `json:"snaps,omitempty"` 50 Users []string `json:"users,omitempty"` 51 } 52 53 // A Snapshot is a collection of archives with a simple metadata json file 54 // (and hashsums of everything). 55 type Snapshot struct { 56 // SetID is the ID of the snapshot set (a snapshot set is the result of a "snap save" invocation) 57 SetID uint64 `json:"set"` 58 // the time this snapshot's data collection was started 59 Time time.Time `json:"time"` 60 61 // information about the snap this data is for 62 Snap string `json:"snap"` 63 Revision snap.Revision `json:"revision"` 64 SnapID string `json:"snap-id,omitempty"` 65 Epoch snap.Epoch `json:"epoch,omitempty"` 66 Summary string `json:"summary"` 67 Version string `json:"version"` 68 69 // the snap's configuration at snapshot time 70 Conf map[string]interface{} `json:"conf,omitempty"` 71 72 // the hash of the archives' data, keyed by archive path 73 // (either 'archive.tgz' for the system archive, or 74 // user/<username>.tgz for each user) 75 SHA3_384 map[string]string `json:"sha3-384"` 76 // the sum of the archive sizes 77 Size int64 `json:"size,omitempty"` 78 // if the snapshot failed to open this will be the reason why 79 Broken string `json:"broken,omitempty"` 80 81 // set if the snapshot was created automatically on snap removal; 82 // note, this is only set inside actual snapshot file for old snapshots; 83 // newer snapd just updates this flag on the fly for snapshots 84 // returned by List(). 85 Auto bool `json:"auto,omitempty"` 86 } 87 88 // IsValid checks whether the snapshot is missing information that 89 // should be there for a snapshot that's just been opened. 90 func (sh *Snapshot) IsValid() bool { 91 return !(sh == nil || sh.SetID == 0 || sh.Snap == "" || sh.Revision.Unset() || len(sh.SHA3_384) == 0 || sh.Time.IsZero()) 92 } 93 94 // A SnapshotSet is a set of snapshots created by a single "snap save". 95 type SnapshotSet struct { 96 ID uint64 `json:"id"` 97 Snapshots []*Snapshot `json:"snapshots"` 98 } 99 100 // Time returns the earliest time in the set. 101 func (ss SnapshotSet) Time() time.Time { 102 if len(ss.Snapshots) == 0 { 103 return time.Time{} 104 } 105 mint := ss.Snapshots[0].Time 106 for _, sh := range ss.Snapshots { 107 if sh.Time.Before(mint) { 108 mint = sh.Time 109 } 110 } 111 return mint 112 } 113 114 // Size returns the sum of the set's sizes. 115 func (ss SnapshotSet) Size() int64 { 116 var sum int64 117 for _, sh := range ss.Snapshots { 118 sum += sh.Size 119 } 120 return sum 121 } 122 123 // SnapshotSets lists the snapshot sets in the system that belong to the 124 // given set (if non-zero) and are for the given snaps (if non-empty). 125 func (client *Client) SnapshotSets(setID uint64, snapNames []string) ([]SnapshotSet, error) { 126 q := make(url.Values) 127 if setID > 0 { 128 q.Add("set", strconv.FormatUint(setID, 10)) 129 } 130 if len(snapNames) > 0 { 131 q.Add("snaps", strings.Join(snapNames, ",")) 132 } 133 134 var snapshotSets []SnapshotSet 135 _, err := client.doSync("GET", "/v2/snapshots", q, nil, nil, &snapshotSets) 136 return snapshotSets, err 137 } 138 139 // ForgetSnapshots permanently removes the snapshot set, limited to the 140 // given snaps (if non-empty). 141 func (client *Client) ForgetSnapshots(setID uint64, snaps []string) (changeID string, err error) { 142 return client.snapshotAction(&snapshotAction{ 143 SetID: setID, 144 Action: "forget", 145 Snaps: snaps, 146 }) 147 } 148 149 // CheckSnapshots verifies the archive checksums in the given snapshot set. 150 // 151 // If snaps or users are non-empty, limit to checking only those 152 // archives of the snapshot. 153 func (client *Client) CheckSnapshots(setID uint64, snaps []string, users []string) (changeID string, err error) { 154 return client.snapshotAction(&snapshotAction{ 155 SetID: setID, 156 Action: "check", 157 Snaps: snaps, 158 Users: users, 159 }) 160 } 161 162 // RestoreSnapshots extracts the given snapshot set. 163 // 164 // If snaps or users are non-empty, limit to checking only those 165 // archives of the snapshot. 166 func (client *Client) RestoreSnapshots(setID uint64, snaps []string, users []string) (changeID string, err error) { 167 return client.snapshotAction(&snapshotAction{ 168 SetID: setID, 169 Action: "restore", 170 Snaps: snaps, 171 Users: users, 172 }) 173 } 174 175 func (client *Client) snapshotAction(action *snapshotAction) (changeID string, err error) { 176 data, err := json.Marshal(action) 177 if err != nil { 178 return "", fmt.Errorf("cannot marshal snapshot action: %v", err) 179 } 180 181 headers := map[string]string{ 182 "Content-Type": "application/json", 183 } 184 185 return client.doAsync("POST", "/v2/snapshots", nil, headers, bytes.NewBuffer(data)) 186 } 187 188 // SnapshotExport streams the requested snapshot set. 189 // 190 // The return value includes the length of the returned stream. 191 func (client *Client) SnapshotExport(setID uint64) (stream io.ReadCloser, contentLength int64, err error) { 192 rsp, err := client.raw(context.Background(), "GET", fmt.Sprintf("/v2/snapshots/%v/export", setID), nil, nil, nil) 193 if err != nil { 194 return nil, 0, err 195 } 196 if rsp.StatusCode != 200 { 197 defer rsp.Body.Close() 198 199 var r response 200 specificErr := r.err(client, rsp.StatusCode) 201 if err != nil { 202 return nil, 0, specificErr 203 } 204 return nil, 0, fmt.Errorf("unexpected status code: %v", rsp.Status) 205 } 206 contentType := rsp.Header.Get("Content-Type") 207 if contentType != SnapshotExportMediaType { 208 return nil, 0, fmt.Errorf("unexpected snapshot export content type %q", contentType) 209 } 210 211 return rsp.Body, rsp.ContentLength, nil 212 } 213 214 // SnapshotImportSet is a snapshot import created by a "snap import-snapshot". 215 type SnapshotImportSet struct { 216 ID uint64 `json:"set-id"` 217 Snaps []string `json:"snaps"` 218 } 219 220 // SnapshotImport imports an exported snapshot set. 221 func (client *Client) SnapshotImport(exportStream io.Reader, size int64) (SnapshotImportSet, error) { 222 headers := map[string]string{ 223 "Content-Type": SnapshotExportMediaType, 224 "Content-Length": strconv.FormatInt(size, 10), 225 } 226 227 var importSet SnapshotImportSet 228 if _, err := client.doSync("POST", "/v2/snapshots", nil, headers, exportStream, &importSet); err != nil { 229 return importSet, err 230 } 231 232 return importSet, nil 233 }