Skip to content

Commit

Permalink
Merge pull request #12073 from MiguelPires/refresh-hold-daemon
Browse files Browse the repository at this point in the history
many: support refresh hold/unhold to API and CLI
  • Loading branch information
mvo5 committed Oct 25, 2022
2 parents 9491882 + adc1154 commit f73eb23
Show file tree
Hide file tree
Showing 9 changed files with 981 additions and 17 deletions.
24 changes: 23 additions & 1 deletion client/snap_op.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2016-2017 Canonical Ltd
* Copyright (C) 2016-2022 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
Expand Down Expand Up @@ -59,6 +59,8 @@ type SnapOptions struct {
Transaction TransactionType `json:"transaction,omitempty"`
QuotaGroupName string `json:"quota-group,omitempty"`
ValidationSets []string `json:"validation-sets,omitempty"`
Time string `json:"time,omitempty"`
HoldLevel string `json:"hold-level,omitempty"`

Users []string `json:"users,omitempty"`
}
Expand Down Expand Up @@ -128,6 +130,8 @@ type multiActionData struct {
IgnoreRunning bool `json:"ignore-running,omitempty"`
Purge bool `json:"purge,omitempty"`
ValidationSets []string `json:"validation-sets,omitempty"`
Time string `json:"time,omitempty"`
HoldLevel string `json:"hold-level,omitempty"`
}

// Install adds the snap with the given name from the given channel (or
Expand Down Expand Up @@ -159,6 +163,22 @@ func (client *Client) RefreshMany(names []string, options *SnapOptions) (changeI
return client.doMultiSnapAction("refresh", names, options)
}

func (client *Client) HoldRefreshes(name string, options *SnapOptions) (changeID string, err error) {
return client.doSnapAction("hold", name, options)
}

func (client *Client) HoldRefreshesMany(names []string, options *SnapOptions) (changeID string, err error) {
return client.doMultiSnapAction("hold", names, options)
}

func (client *Client) UnholdRefreshes(name string, options *SnapOptions) (changeID string, err error) {
return client.doSnapAction("unhold", name, options)
}

func (client *Client) UnholdRefreshesMany(names []string, options *SnapOptions) (changeID string, err error) {
return client.doMultiSnapAction("unhold", names, options)
}

func (client *Client) Enable(name string, options *SnapOptions) (changeID string, err error) {
return client.doSnapAction("enable", name, options)
}
Expand Down Expand Up @@ -236,6 +256,8 @@ func (client *Client) doMultiSnapActionFull(actionName string, snaps []string, o
action.IgnoreRunning = options.IgnoreRunning
action.Purge = options.Purge
action.ValidationSets = options.ValidationSets
action.Time = options.Time
action.HoldLevel = options.HoldLevel
}

data, err := json.Marshal(&action)
Expand Down
41 changes: 41 additions & 0 deletions client/snap_op_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ var ops = []struct {
{(*client.Client).Enable, "enable"},
{(*client.Client).Disable, "disable"},
{(*client.Client).Switch, "switch"},
{(*client.Client).HoldRefreshes, "hold"},
{(*client.Client).UnholdRefreshes, "unhold"},
}

var multiOps = []struct {
Expand All @@ -58,6 +60,8 @@ var multiOps = []struct {
{(*client.Client).RefreshMany, "refresh"},
{(*client.Client).InstallMany, "install"},
{(*client.Client).RemoveMany, "remove"},
{(*client.Client).HoldRefreshesMany, "hold"},
{(*client.Client).UnholdRefreshesMany, "unhold"},
}

func (cs *clientSuite) TestClientOpSnapServerError(c *check.C) {
Expand Down Expand Up @@ -836,3 +840,40 @@ func (cs *clientSuite) TestClientRefreshWithValidationSets(c *check.C) {
})
c.Check(cs.req.Header["Content-Type"], check.DeepEquals, []string{"application/json"})
}

func (cs *clientSuite) TestClientHoldMany(c *check.C) {
cs.status = 202
cs.rsp = `{
"change": "12",
"status-code": 202,
"type": "async"
}`

chgID, err := cs.cli.HoldRefreshesMany([]string{"foo", "bar"}, &client.SnapOptions{
Time: "forever",
HoldLevel: "general",
})
c.Assert(err, check.IsNil)
c.Check(chgID, check.Equals, "12")

type req struct {
Action string `json:"action"`
Snaps []string `json:"snaps"`
Time string `json:"time"`
HoldLevel string `json:"hold-level"`
}
body, err := ioutil.ReadAll(cs.req.Body)
c.Assert(err, check.IsNil)

var decodedBody req
err = json.Unmarshal(body, &decodedBody)
c.Assert(err, check.IsNil)

c.Check(decodedBody, check.DeepEquals, req{
Action: "hold",
Snaps: []string{"foo", "bar"},
Time: "forever",
HoldLevel: "general",
})
c.Check(cs.req.Header["Content-Type"], check.DeepEquals, []string{"application/json"})
}
111 changes: 110 additions & 1 deletion cmd/snap/cmd_snap_op.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
* Copyright (C) 2016-2017 Canonical Ltd
* Copyright (C) 2016-2022 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
Expand Down Expand Up @@ -94,6 +94,18 @@ have developer access to the snap, either directly or through the
store's collaboration feature, and to be logged in (see 'snap help login').
Note a later refresh will typically undo a revision override.
Hold (--hold) is used to postpone snap refresh updates for all snaps when no
snaps are specified, or for the specified snaps.
When no snaps are specified --hold is only effective on auto-refreshes and will
not block either general refresh requests from 'snap refresh' or specific snap
requests from 'snap refresh target-snap'.
When specific snaps are mentioned --hold is effective on their auto-refreshes
and will also silently block general refresh requests from 'snap refresh' of
those snaps while explicit targeted 'snap refresh target-snap' will not be
blocked.
`)

var longTryHelp = i18n.G(`
Expand Down Expand Up @@ -682,6 +694,8 @@ type cmdRefresh struct {
IgnoreValidation bool `long:"ignore-validation"`
IgnoreRunning bool `long:"ignore-running" hidden:"yes"`
Transaction client.TransactionType `long:"transaction" default:"per-snap" choice:"all-snaps" choice:"per-snap"`
Hold string `long:"hold" optional:"yes" optional-value:"forever"`
Unhold bool `long:"unhold"`
Positional struct {
Snaps []installedSnapName `positional-arg-name:"<snap>"`
} `positional-args:"yes"`
Expand Down Expand Up @@ -841,6 +855,20 @@ func (x *cmdRefresh) Execute([]string) error {
return nil
}

otherFlags := x.Amend || x.Revision != "" || x.Cohort != "" ||
x.LeaveCohort || x.List || x.Time || x.IgnoreValidation || x.IgnoreRunning ||
x.Transaction != client.TransactionPerSnap

if x.Hold != "" && (x.Unhold || otherFlags) {
return errors.New(i18n.G("cannot use --hold with other flags"))
} else if x.Unhold && (x.Hold != "" || otherFlags) {
return errors.New(i18n.G("cannot use --unhold with other flags"))
} else if x.Hold != "" {
return x.holdRefreshes()
} else if x.Unhold {
return x.unholdRefreshes()
}

names := installedSnapNames(x.Positional.Snaps)
if len(names) == 1 {
opts := &client.SnapOptions{
Expand Down Expand Up @@ -874,6 +902,83 @@ func (x *cmdRefresh) Execute([]string) error {
return x.refreshMany(names, opts)
}

func (x *cmdRefresh) holdRefreshes() (err error) {
var opts client.SnapOptions

if x.Hold == "forever" {
opts.Time = "forever"
} else {
dur, err := time.ParseDuration(x.Hold)
if err != nil {
return fmt.Errorf("hold value must be a number of hours or minutes (e.g., 72h): %v", err)
}

opts.Time = timeNow().Add(dur).Format(time.RFC3339)
}

names := installedSnapNames(x.Positional.Snaps)
var changeID string
opts.HoldLevel = "general"
if len(names) == 0 {
opts.HoldLevel = "auto-refresh"
}
if len(names) == 1 {
changeID, err = x.client.HoldRefreshes(names[0], &opts)
} else {
changeID, err = x.client.HoldRefreshesMany(names, &opts)
}

if err != nil {
return err
}

_, err = x.wait(changeID)
if err != nil {
if err == noWait {
return nil
}
return err
}

if len(names) == 0 {
fmt.Fprintf(Stdout, i18n.G("Auto-refresh of all snaps held until %s\n"), opts.Time)
} else {
fmt.Fprintf(Stdout, i18n.G("General refreshes of %s held until %s\n"), strutil.Quoted(names), opts.Time)
}

return nil
}

func (x *cmdRefresh) unholdRefreshes() (err error) {
names := installedSnapNames(x.Positional.Snaps)
var changeID string
if len(names) == 1 {
changeID, err = x.client.UnholdRefreshes(names[0], nil)
} else {
changeID, err = x.client.UnholdRefreshesMany(names, nil)
}

if err != nil {
return err
}

_, err = x.wait(changeID)
if err != nil {
if err == noWait {
return nil
}
return err
}

if len(names) == 0 {
fmt.Fprintf(Stdout, i18n.G("Removed auto-refresh hold on all snaps\n"))
} else {
fmt.Fprintf(Stdout, i18n.G("Removed general refresh hold of %s\n"), strutil.Quoted(names))
}

return nil
}

type cmdTry struct {
waitMixin

Expand Down Expand Up @@ -1182,6 +1287,10 @@ func init() {
"leave-cohort": i18n.G("Refresh the snap out of its cohort"),
// TRANSLATORS: This should not start with a lowercase letter.
"transaction": i18n.G("Have one transaction per-snap or one for all the specified snaps"),
// TRANSLATORS: This should not start with a lowercase letter.
"hold": i18n.G("Hold refreshes for a specified duration (or indefinitely, if none is specified)"),
// TRANSLATORS: This should not start with a lowercase letter.
"unhold": i18n.G("Remove refresh hold"),
}), nil)
addCommand("try", shortTryHelp, longTryHelp, func() flags.Commander { return &cmdTry{} }, waitDescs.also(modeDescs), nil)
addCommand("enable", shortEnableHelp, longEnableHelp, func() flags.Commander { return &cmdEnable{} }, waitDescs, nil)
Expand Down

0 comments on commit f73eb23

Please sign in to comment.