summaryrefslogtreecommitdiff
diff options
-rw-r--r--asserts/account_key.go47
-rw-r--r--asserts/account_key_test.go24
-rw-r--r--asserts/assertstest/assertstest.go8
-rw-r--r--asserts/assertstest/assertstest_test.go4
-rw-r--r--asserts/database_test.go1
-rw-r--r--asserts/gpgkeypairmgr.go3
-rw-r--r--asserts/systestkeys/trusted.go42
-rw-r--r--cmd/snap/cmd_abort.go7
-rw-r--r--cmd/snap/cmd_ack.go7
-rw-r--r--cmd/snap/cmd_booted.go2
-rw-r--r--cmd/snap/cmd_buy.go11
-rw-r--r--cmd/snap/cmd_buy_test.go2
-rw-r--r--cmd/snap/cmd_changes.go4
-rw-r--r--cmd/snap/cmd_connect.go7
-rw-r--r--cmd/snap/cmd_create_key.go7
-rw-r--r--cmd/snap/cmd_create_user.go17
-rw-r--r--cmd/snap/cmd_delete_key.go7
-rw-r--r--cmd/snap/cmd_disconnect.go7
-rw-r--r--cmd/snap/cmd_download.go11
-rw-r--r--cmd/snap/cmd_export_key.go11
-rw-r--r--cmd/snap/cmd_find.go8
-rw-r--r--cmd/snap/cmd_first_boot.go2
-rw-r--r--cmd/snap/cmd_get.go20
-rw-r--r--cmd/snap/cmd_help.go5
-rw-r--r--cmd/snap/cmd_interfaces.go11
-rw-r--r--cmd/snap/cmd_keys.go4
-rw-r--r--cmd/snap/cmd_known.go12
-rw-r--r--cmd/snap/cmd_list.go2
-rw-r--r--cmd/snap/cmd_login.go10
-rw-r--r--cmd/snap/cmd_logout.go2
-rw-r--r--cmd/snap/cmd_prepare_image.go19
-rw-r--r--cmd/snap/cmd_run.go15
-rw-r--r--cmd/snap/cmd_set.go14
-rw-r--r--cmd/snap/cmd_shell.go7
-rw-r--r--cmd/snap/cmd_sign.go4
-rw-r--r--cmd/snap/cmd_sign_build.go20
-rw-r--r--cmd/snap/cmd_snap_op.go70
-rw-r--r--cmd/snap/main.go39
-rw-r--r--cmd/snap/notes.go11
-rw-r--r--tests/lib/assertions/developer1.account-key22
-rw-r--r--tests/lib/assertions/testrootorg-store.account-key21
-rw-r--r--tests/main/ack/developer1.account19
-rw-r--r--tests/main/ack/task.yaml6
-rw-r--r--tests/main/ack/testrootorg-store.account-key29
-rw-r--r--tests/main/login/missing_email_error.exp2
45 files changed, 364 insertions, 239 deletions
diff --git a/asserts/account_key.go b/asserts/account_key.go
index 3e01b4ad12..8987331a23 100644
--- a/asserts/account_key.go
+++ b/asserts/account_key.go
@@ -108,27 +108,26 @@ func (ak *AccountKey) checkConsistency(db RODatabase, acck *AccountKey) error {
if err != nil {
return err
}
- // XXX: Make this unconditional once account-key assertions are required to have a name.
- if ak.Name() != "" {
- // Check that we don't end up with multiple keys with
- // different IDs but the same account-id and name.
- // Note that this is a non-transactional check-then-add, so
- // is not a hard guarantee. Backstores that can implement a
- // unique constraint should do so.
- assertions, err := db.FindMany(AccountKeyType, map[string]string{
- "account-id": ak.AccountID(),
- "name": ak.Name(),
- })
- if err != nil && err != ErrNotFound {
- return err
- }
- for _, assertion := range assertions {
- existingAccKey := assertion.(*AccountKey)
- if ak.PublicKeyID() != existingAccKey.PublicKeyID() {
- return fmt.Errorf("account-key assertion for %q with ID %q has the same name %q as existing ID %q", ak.AccountID(), ak.PublicKeyID(), ak.Name(), existingAccKey.PublicKeyID())
- }
+
+ // Check that we don't end up with multiple keys with
+ // different IDs but the same account-id and name.
+ // Note that this is a non-transactional check-then-add, so
+ // is not a hard guarantee. Backstores that can implement a
+ // unique constraint should do so.
+ assertions, err := db.FindMany(AccountKeyType, map[string]string{
+ "account-id": ak.AccountID(),
+ "name": ak.Name(),
+ })
+ if err != nil && err != ErrNotFound {
+ return err
+ }
+ for _, assertion := range assertions {
+ existingAccKey := assertion.(*AccountKey)
+ if ak.PublicKeyID() != existingAccKey.PublicKeyID() {
+ return fmt.Errorf("account-key assertion for %q with ID %q has the same name %q as existing ID %q", ak.AccountID(), ak.PublicKeyID(), ak.Name(), existingAccKey.PublicKeyID())
}
}
+
return nil
}
@@ -148,13 +147,9 @@ func assembleAccountKey(assert assertionBase) (Assertion, error) {
return nil, err
}
- // XXX: We should require name to be present after backfilling existing assertions.
- _, ok := assert.headers["name"]
- if ok {
- _, err = checkStringMatches(assert.headers, "name", validAccountKeyName)
- if err != nil {
- return nil, err
- }
+ _, err = checkStringMatches(assert.headers, "name", validAccountKeyName)
+ if err != nil {
+ return nil, err
}
since, err := checkRFC3339Date(assert.headers, "since")
diff --git a/asserts/account_key_test.go b/asserts/account_key_test.go
index 37887ce183..366bffcc52 100644
--- a/asserts/account_key_test.go
+++ b/asserts/account_key_test.go
@@ -85,27 +85,6 @@ func (aks *accountKeySuite) TestDecodeOK(c *C) {
c.Check(accKey.Since(), Equals, aks.since)
}
-func (aks *accountKeySuite) TestDecodeNoName(c *C) {
- // XXX: remove this test once name is mandatory
- encoded := "type: account-key\n" +
- "authority-id: canonical\n" +
- "account-id: acc-id1\n" +
- "public-key-sha3-384: " + aks.keyID + "\n" +
- aks.sinceLine +
- fmt.Sprintf("body-length: %v", len(aks.pubKeyBody)) + "\n" +
- "sign-key-sha3-384: Jv8_JiHiIzJVcO9M55pPdqSDWUvuhfDIBJUS-3VW7F_idjix7Ffn5qMxB21ZQuij" + "\n\n" +
- aks.pubKeyBody + "\n\n" +
- "AXNpZw=="
- a, err := asserts.Decode([]byte(encoded))
- c.Assert(err, IsNil)
- c.Check(a.Type(), Equals, asserts.AccountKeyType)
- accKey := a.(*asserts.AccountKey)
- c.Check(accKey.AccountID(), Equals, "acc-id1")
- c.Check(accKey.Name(), Equals, "")
- c.Check(accKey.PublicKeyID(), Equals, aks.keyID)
- c.Check(accKey.Since(), Equals, aks.since)
-}
-
func (aks *accountKeySuite) TestUntil(c *C) {
untilSinceLine := "until: " + aks.since.Format(time.RFC3339) + "\n"
@@ -164,8 +143,7 @@ func (aks *accountKeySuite) TestDecodeInvalidHeaders(c *C) {
invalidHeaderTests := []struct{ original, invalid, expectedErr string }{
{"account-id: acc-id1\n", "", `"account-id" header is mandatory`},
{"account-id: acc-id1\n", "account-id: \n", `"account-id" header should not be empty`},
- // XXX: enable this once name is mandatory
- // {"name: default\n", "", `"name" header is mandatory`},
+ {"name: default\n", "", `"name" header is mandatory`},
{"name: default\n", "name: \n", `"name" header should not be empty`},
{"name: default\n", "name: a b\n", `"name" header contains invalid characters: "a b"`},
{"name: default\n", "name: -default\n", `"name" header contains invalid characters: "-default"`},
diff --git a/asserts/assertstest/assertstest.go b/asserts/assertstest/assertstest.go
index 45a7d69d3b..45d605200c 100644
--- a/asserts/assertstest/assertstest.go
+++ b/asserts/assertstest/assertstest.go
@@ -192,6 +192,9 @@ func NewAccountKey(db SignerDB, acct *asserts.Account, otherHeaders map[string]i
}
otherHeaders["account-id"] = acct.AccountID()
otherHeaders["public-key-sha3-384"] = pubKey.ID()
+ if otherHeaders["name"] == nil {
+ otherHeaders["name"] = "default"
+ }
if otherHeaders["since"] == nil {
otherHeaders["since"] = time.Now().Format(time.RFC3339)
}
@@ -272,6 +275,7 @@ func NewStoreStack(authorityID string, rootPrivKey, storePrivKey asserts.Private
"timestamp": ts,
}, "")
trustedKey := NewAccountKey(rootSigning, trustedAcct, map[string]interface{}{
+ "name": "root",
"since": ts,
}, rootPrivKey.PublicKey(), "")
trusted := []asserts.Assertion{trustedAcct, trustedKey}
@@ -287,7 +291,9 @@ func NewStoreStack(authorityID string, rootPrivKey, storePrivKey asserts.Private
if err != nil {
panic(err)
}
- storeKey := NewAccountKey(rootSigning, trustedAcct, nil, storePrivKey.PublicKey(), "")
+ storeKey := NewAccountKey(rootSigning, trustedAcct, map[string]interface{}{
+ "name": "store",
+ }, storePrivKey.PublicKey(), "")
err = db.Add(storeKey)
if err != nil {
panic(err)
diff --git a/asserts/assertstest/assertstest_test.go b/asserts/assertstest/assertstest_test.go
index 1cdb33dacf..3c1b0c9b18 100644
--- a/asserts/assertstest/assertstest_test.go
+++ b/asserts/assertstest/assertstest_test.go
@@ -84,6 +84,7 @@ func (s *helperSuite) TestStoreStack(c *C) {
c.Check(store.TrustedAccount.IsCertified(), Equals, true)
c.Check(store.TrustedKey.AccountID(), Equals, "super")
+ c.Check(store.TrustedKey.Name(), Equals, "root")
db, err := asserts.OpenDatabase(&asserts.DatabaseConfig{
Backstore: asserts.NewMemoryBackstore(),
@@ -97,6 +98,7 @@ func (s *helperSuite) TestStoreStack(c *C) {
c.Check(storeAccKey.AccountID(), Equals, "super")
c.Check(storeAccKey.AccountID(), Equals, store.AuthorityID)
c.Check(storeAccKey.PublicKeyID(), Equals, store.KeyID)
+ c.Check(storeAccKey.Name(), Equals, "store")
acct := assertstest.NewAccount(store, "devel1", nil, "")
c.Check(acct.Username(), Equals, "devel1")
@@ -115,4 +117,6 @@ func (s *helperSuite) TestStoreStack(c *C) {
err = db.Add(acctKey)
c.Assert(err, IsNil)
+
+ c.Check(acctKey.Name(), Equals, "default")
}
diff --git a/asserts/database_test.go b/asserts/database_test.go
index e78b5d9104..20a2de22cc 100644
--- a/asserts/database_test.go
+++ b/asserts/database_test.go
@@ -671,6 +671,7 @@ func (safs *signAddFindSuite) TestDontLetAddConfusinglyAssertionClashingWithTrus
"authority-id": "canonical",
"account-id": "canonical",
"public-key-sha3-384": safs.signingKeyID,
+ "name": "default",
"since": now.Format(time.RFC3339),
"until": now.AddDate(1, 0, 0).Format(time.RFC3339),
}
diff --git a/asserts/gpgkeypairmgr.go b/asserts/gpgkeypairmgr.go
index f048f538e3..9024975172 100644
--- a/asserts/gpgkeypairmgr.go
+++ b/asserts/gpgkeypairmgr.go
@@ -200,6 +200,8 @@ func (gkm *GPGKeypairManager) Walk(consider func(privk PrivateKey, fingerprint s
switch {
case strings.HasPrefix(lines[k], "fpr:"):
fprFields := strings.Split(lines[k], ":")
+ // extract "Field 10 - User-ID"
+ // A FPR record stores the fingerprint here.
if len(fprFields) < 10 {
break Loop
}
@@ -213,6 +215,7 @@ func (gkm *GPGKeypairManager) Walk(consider func(privk PrivateKey, fingerprint s
}
case strings.HasPrefix(lines[k], "uid:"):
uidFields := strings.Split(lines[k], ":")
+ // extract "*** Field 10 - User-ID"
if len(uidFields) < 10 {
break Loop
}
diff --git a/asserts/systestkeys/trusted.go b/asserts/systestkeys/trusted.go
index 72390457de..90834f6823 100644
--- a/asserts/systestkeys/trusted.go
+++ b/asserts/systestkeys/trusted.go
@@ -113,6 +113,7 @@ C0QC8xuHSvOv3YRtzKna3smAfRlB
authority-id: testrootorg
public-key-sha3-384: hIedp1AvrWlcDI4uS_qjoFLzjKl5enu4G2FYJpgB3Pj-tUzGlTQBxMBsBmi-tnJR
account-id: testrootorg
+name: test-root
since: 2016-08-11T18:30:57+02:00
body-length: 717
sign-key-sha3-384: hIedp1AvrWlcDI4uS_qjoFLzjKl5enu4G2FYJpgB3Pj-tUzGlTQBxMBsBmi-tnJR
@@ -128,16 +129,16 @@ a1zzJoTKTqYWX9B+ZfENGKJUnhTP0x7Cm6lg3EUGay/b5hsA4DBoqShuf/N0jVLojdhxi3Ck/DBN
lqCD0zy4uzvinjX+b4ay+LKBE3N15AsfEkWIwzI+1OdDlOWWqOxJkM6lrQ5hRQ1fHZoCiGjHbjeE
1RIFO2TAw2tpyUcAEQEAAQ==
-AcLBUgQAAQoABgUCV6yoQQAAAaQQAJ+6saqG2DElfKZBbmthhlN8fHXSR8RX5LnbfE5zd4vTbthC
-//MjJtpUwq5vpM1/XB9p8cGZD1UlEdUa8l9N8oGSfJARZ+rAsPLlguzSoV4p6ph16HPlvBVt5npB
-DqK/Oxw+mtx2cnxn8X9Zw3wyz4mXp3cuu7PwSQvFSvcrxoNIOVkaHYEytQqqvZp8Lq1AirllGEL8
-EocRLOiG0O99P3BJytLWLYePRJ6qToiz58WuZEVj2lkC+HqrIoVrjgFAUlq100R15xgc4WtNFdWr
-hInauQxco+/vwHvCgxa/Ky+dABY/W+D9fuM7kjrhh/zqQiiIRGhfAndoi9I7Q/FISrECckZEN0yb
-N3ntOkTJpCnonTfGW6S0VDfGjQreekEU4nwYk3ewdCDY9n9N4zOPmylqU3u2lLJJNsi9rHWWYTOM
-9tXI1yocgrbKaQ8WQQeBQx0SVFdWOl+NvsGcKvs/7qm7SWr/pXo4F+MabqIzX1bb/WvgarpDiGYB
-p+ELFp1KRq+vS0qtP1fggrhyGmuQFeSf411cXKa21h870GcaBlmbZZMB/C1lD5fPG1WsrT7DO2Yu
-Uhf1Q4y+kAgxqL7zZUqJogpxNgw3He66uB7V7hf/UpOfFNeQZaZDCfSzbz/fNzNvNaqiMh6OUrbd
-k9v1ImHrPI6+o+xjCbMc2xdRcvM+
+AcLBXAQAAQoABgUCV8656QAKCRBMcZp594FxpNWlEADQgBlROdBTHpdZ3/9BbasxenUC3VXusMeK
+0DmnsHrsAsyVk6xiHQQ3hWxvXKWoDkDsOhUqcQTsDBcIaZ18+qwpQciyItd+w3d7SSJ+MKSUpwsB
+NOdgw1ykj7l1M/W7xAAPscFoV1xVSk9+rsLYFYDe23R+ecyotSmF+4QHj5b+hXeVIOUaqQTl5xPC
+h0zVYNIUWv42q4Z+hiBS8+8UJ0G+7z/27XORkGHY6TXCt0aph7s5egr8Lm+/jq7c95HVsa7DwSpv
+SqPajRnlyLiHFXUYAUPEU9oDgPwtLsqUkFfrv1WZ3ja1rDexgKBta+8BRyCAq3gPcMAjhiHXdjoW
+90p893l9N6K82RiEOO9ic0pEezjQldg97oU+ajXNm3ryns+HX6hRd39rpzIsrbVdbCqun4RwMbCM
+EVxgC/cuxMGcS40Co3O8wG3H/WIWOqcRQfolQTexmyzQljYt9WyWJdXmtPtaMzQGbOqE/dIjOK9j
+xvrghVU4kX6fJFwPi+azMrluHV+WGSVxPCuLW8o2aipjOd1/bUQCL5OwRuaEWuLCiV01J8H/JjWV
+hL4gGVqEM2KEPIDwY2yqX36jE7uN9O+mIPnS4Tdj0JQ5ZD1qh34wv+4QvhgNeyP120nuS1ykO9X0
+A806uPC5QK1+cgRMUz8zJ0afDNwE/DvpBQvE5CIi9A==
`
TestStorePrivKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
@@ -204,6 +205,7 @@ o7ZSZ/h/bUY1EjE2
authority-id: testrootorg
public-key-sha3-384: XCIC_Wvj9_hiAt0b10sDon74oGr3a6xGODkMZqrj63ZzNYUD5N87-ojjPoeN7f1Y
account-id: testrootorg
+name: test-store
since: 2016-08-11T18:42:22+02:00
body-length: 717
sign-key-sha3-384: hIedp1AvrWlcDI4uS_qjoFLzjKl5enu4G2FYJpgB3Pj-tUzGlTQBxMBsBmi-tnJR
@@ -219,16 +221,16 @@ iAIwA4DpGMmFJ26maqVzJuiLvicri2FR/sJaSA24N8HbGne3gSS7WrSQS+jKe3IZPVy64NCoGvrW
o/HvTeqsIfihKPEpXm8QVtjNhtkVn3RdIUgOaNWyAfnZ4dW1TVIATe+OHDw2TNyImTjE0x75nL6B
1/Rrn+9VP9Swhv8AEQEAAQ==
-AcLBUgQAAQoABgUCV6yq7gAAhaYQAMIVYhta2uUvm5PXApdXmZFWr+iZYfkAZW8PEMOsuYVHbDoH
-oA7dpO0EwZXl/mCgGjNNc4nUmqQLBiIwwrcnmcYSRl2Xz+u+ssou4YMueXOD2tHo1N2J39SKpS72
-VqQsnF77Qylgdp2j7Q9lJrU0qHz0M195OJXNSppfdHYeWptfsO02cApPobU9s6KT5VggVg1ushNM
-1u97A+uvoClfJ53PPafC0kr1+vwFVPj+mko4gc7sIB42xwz+YeR47CgSaT8i8K1u5ouaHCNxe61+
-siQxAdIOv+hOAWAOMoOWZjxh5K7J9A1Dc18EMyggf716IUCBtkvDKfwcXFcoic1X6EVPO5kNhlh7
-aLFS8UVsBPaZKnJm3ZFudhYQUmZt22ntslgGrq9+NBeh9i6nswUPKdj2idHkyQsjnYgYyTpEH/xh
-sBqkqkedPkUtn+tP5dS4TP/L3Xq/q2tQbA4+gNVbZXIo8g8wfpsP8+sIOnEV5UcoxaO3oI3YUJrN
-oFGmC7No802XKG1ZNHhBtMSaan0pafWrBrHn+axT9Jbl/1B73TYg3zQWOpDuSpH3SU+gXJ/eukUb
-LvC8UWZM9YEhu/ZINSvSDQzvobV/NVrHWEJBXxrc3CwseFDgQq+Yz/CFGud79z2mS8lNHKSqQdFK
-3Bd0HG1HheYcX0nGIva0KIG0Sgf/
+AcLBXAQAAQoABgUCV866kwAKCRBMcZp594FxpHWHD/9AaZXqyT/Zsmq/VzmAMpd9JvCH4PHQKtAP
+bXfP2Dnpa2wk2wuzQuSWunR8NDRyVh/aNVeTEZ9dFm/B8LR+U2O4rsHmFSeicmsTmo9u/HouRdEU
+zeSc6cbAxMPpfNSjr5J+URLjGRT6oX5fEBmRPx/OC9pEIScMx7uKmTKEnuyMzLRNN/6HiGWKrFCo
+nJdKkwRXrkCHyXWAOv1GumT7NDuyFcjAqt/UdHliTZkDBImKOsBmBVXMUjg7HCSS2uq/5WjStJ+B
+JHQ4GSsXBvVINs6BncNWcvV6mCQ73D57MzGhqo997Zb4tSrn7UNGWK7GLCzV3e/pFlG7pw6HbgnQ
++rxU2Oj/TPVw0tcnUiRl2ttKpm+nua0Cl+MD+Gx0KXLAVp0ZGOQ9yGyP9AePFzcOR8SlRIgxi0EI
+iJkSeYilqoKo3AJhnICRiqvAca2TGJoiJUryEgZ8jbTOElfaF2p+y0xvXGlWbKZm1gzGyvFM5fV5
+hJTlp/am+2uVn6U8wPACir4PrbuXYo7L4MIXww2OEO0ruBIaLARbc5IutSWmw6AEYQUxtsa9bdHV
+Zin7LGbEj6lZm8GycWQwh4B6Vnt6dJRIyPc/9G7uM8Ds/2Wa7+yAxhiPqm8DwlbOYh1npw4X4TLD
+IMGnTv5N3zllI+Xz4rqJzNTzEbvOIcrqWxCedQe79A==
`
)
diff --git a/cmd/snap/cmd_abort.go b/cmd/snap/cmd_abort.go
index 5835df95b9..7fd468f327 100644
--- a/cmd/snap/cmd_abort.go
+++ b/cmd/snap/cmd_abort.go
@@ -27,7 +27,7 @@ import (
type cmdAbort struct {
Positional struct {
- ID string `positional-arg-name:"change-id"`
+ ID string
} `positional-args:"yes" required:"yes"`
}
@@ -43,7 +43,10 @@ func init() {
longAbortHelp,
func() flags.Commander {
return &cmdAbort{}
- })
+ },
+ nil,
+ []argDesc{{name: i18n.G("<change-id>")}},
+ )
}
func (x *cmdAbort) Execute(args []string) error {
diff --git a/cmd/snap/cmd_ack.go b/cmd/snap/cmd_ack.go
index f96ab4550c..c8551ce05a 100644
--- a/cmd/snap/cmd_ack.go
+++ b/cmd/snap/cmd_ack.go
@@ -29,7 +29,7 @@ import (
type cmdAck struct {
AckOptions struct {
- AssertionFile string `positional-arg-name:"<assertion file>" description:"assertion file"`
+ AssertionFile string
} `positional-args:"true" required:"true"`
}
@@ -48,7 +48,10 @@ database.
func init() {
addCommand("ack", shortAckHelp, longAckHelp, func() flags.Commander {
return &cmdAck{}
- })
+ }, nil, []argDesc{{
+ name: i18n.G("<assertion file>"),
+ desc: i18n.G("assertion file"),
+ }})
}
func (x *cmdAck) Execute(args []string) error {
diff --git a/cmd/snap/cmd_booted.go b/cmd/snap/cmd_booted.go
index 6af94f3c62..e74fdef6ab 100644
--- a/cmd/snap/cmd_booted.go
+++ b/cmd/snap/cmd_booted.go
@@ -39,7 +39,7 @@ func init() {
"internal",
func() flags.Commander {
return &cmdBooted{}
- })
+ }, nil, nil)
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_buy.go b/cmd/snap/cmd_buy.go
index 9d58ddd14f..109b3da42c 100644
--- a/cmd/snap/cmd_buy.go
+++ b/cmd/snap/cmd_buy.go
@@ -44,17 +44,22 @@ var positiveResponse = map[string]bool{
}
type cmdBuy struct {
- Currency string `long:"currency" description:"ISO 4217 code for currency (https://en.wikipedia.org/wiki/ISO_4217)"`
+ Currency string `long:"currency"`
Positional struct {
- SnapName string `positional-arg-name:"<snap-name>"`
+ SnapName string
} `positional-args:"yes" required:"yes"`
}
func init() {
addCommand("buy", shortBuyHelp, longBuyHelp, func() flags.Commander {
return &cmdBuy{}
- })
+ }, map[string]string{
+ "currency": i18n.G("ISO 4217 code for currency (https://en.wikipedia.org/wiki/ISO_4217)"),
+ }, []argDesc{{
+ name: "<snap>",
+ desc: i18n.G("snap name"),
+ }})
}
func (x *cmdBuy) Execute(args []string) error {
diff --git a/cmd/snap/cmd_buy_test.go b/cmd/snap/cmd_buy_test.go
index 313fa88df8..a519e822e2 100644
--- a/cmd/snap/cmd_buy_test.go
+++ b/cmd/snap/cmd_buy_test.go
@@ -75,7 +75,7 @@ func (s *buyTestMockSnapServer) checkCounts() {
func (s *SnapSuite) TestBuyHelp(c *check.C) {
_, err := snap.Parser().ParseArgs([]string{"buy"})
c.Assert(err, check.NotNil)
- c.Check(err.Error(), check.Equals, "the required argument `<snap-name>` was not provided")
+ c.Check(err.Error(), check.Equals, "the required argument `<snap>` was not provided")
c.Check(s.Stdout(), check.Equals, "")
c.Check(s.Stderr(), check.Equals, "")
}
diff --git a/cmd/snap/cmd_changes.go b/cmd/snap/cmd_changes.go
index afe9c79195..11c96f379a 100644
--- a/cmd/snap/cmd_changes.go
+++ b/cmd/snap/cmd_changes.go
@@ -51,8 +51,8 @@ type cmdChange struct {
}
func init() {
- addCommand("changes", shortChangesHelp, longChangesHelp, func() flags.Commander { return &cmdChanges{} })
- addCommand("change", shortChangeHelp, longChangeHelp, func() flags.Commander { return &cmdChange{} })
+ addCommand("changes", shortChangesHelp, longChangesHelp, func() flags.Commander { return &cmdChanges{} }, nil, nil)
+ addCommand("change", shortChangeHelp, longChangeHelp, func() flags.Commander { return &cmdChange{} }, nil, nil)
}
type changesByTime []*client.Change
diff --git a/cmd/snap/cmd_connect.go b/cmd/snap/cmd_connect.go
index d8ad807637..cc32889983 100644
--- a/cmd/snap/cmd_connect.go
+++ b/cmd/snap/cmd_connect.go
@@ -27,8 +27,8 @@ import (
type cmdConnect struct {
Positionals struct {
- Offer SnapAndName `positional-arg-name:"<snap>:<plug>" required:"true"`
- Use SnapAndName `positional-arg-name:"<snap>:<slot>" required:"true"`
+ Offer SnapAndName `required:"true"`
+ Use SnapAndName `required:"true"`
} `positional-args:"true" required:"true"`
}
@@ -58,6 +58,9 @@ proceeds as above.
func init() {
addCommand("connect", shortConnectHelp, longConnectHelp, func() flags.Commander {
return &cmdConnect{}
+ }, nil, []argDesc{
+ {name: i18n.G("<snap>:<plug>")},
+ {name: i18n.G("<snap>:<slot>")},
})
}
diff --git a/cmd/snap/cmd_create_key.go b/cmd/snap/cmd_create_key.go
index 89c2fc3988..640e4bf1b1 100644
--- a/cmd/snap/cmd_create_key.go
+++ b/cmd/snap/cmd_create_key.go
@@ -32,7 +32,7 @@ import (
type cmdCreateKey struct {
Positional struct {
- KeyName string `positional-arg-name:"<key-name>" description:"name of key to create; defaults to 'default'"`
+ KeyName string
} `positional-args:"true"`
}
@@ -42,7 +42,10 @@ func init() {
i18n.G("Create a cryptographic key pair that can be used for signing assertions."),
func() flags.Commander {
return &cmdCreateKey{}
- })
+ }, nil, []argDesc{{
+ name: i18n.G("<key-name>"),
+ desc: i18n.G("name of key to create; defaults to 'default'"),
+ }})
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_create_user.go b/cmd/snap/cmd_create_user.go
index 6b07a9ab9d..ab8dc81a04 100644
--- a/cmd/snap/cmd_create_user.go
+++ b/cmd/snap/cmd_create_user.go
@@ -38,15 +38,24 @@ An account can be setup at https://login.ubuntu.com.
`)
type cmdCreateUser struct {
- JSON bool `long:"json" description:"output results in JSON format"`
- Sudoer bool `long:"sudoer" description:"grant sudo access to the created user"`
+ JSON bool `long:"json"`
+ Sudoer bool `long:"sudoer"`
Positional struct {
- Email string `positional-arg-name:"email"`
+ Email string
} `positional-args:"yes"`
}
func init() {
- addCommand("create-user", shortCreateUserHelp, longCreateUserHelp, func() flags.Commander { return &cmdCreateUser{} })
+ addCommand("create-user", shortCreateUserHelp, longCreateUserHelp, func() flags.Commander { return &cmdCreateUser{} },
+ map[string]string{
+ "json": i18n.G("output results in JSON format"),
+ "sudoer": i18n.G("grant sudo access to the created user"),
+ }, []argDesc{{
+ // TRANSLATORS: noun
+ name: i18n.G("<email>"),
+ // TRANSLATORS: note users on login.ubuntu.com can have multiple email addresses
+ desc: i18n.G("an email of a user on login.ubuntu.com"),
+ }})
}
func (x *cmdCreateUser) Execute(args []string) error {
diff --git a/cmd/snap/cmd_delete_key.go b/cmd/snap/cmd_delete_key.go
index c8412e9781..f61611c141 100644
--- a/cmd/snap/cmd_delete_key.go
+++ b/cmd/snap/cmd_delete_key.go
@@ -28,7 +28,7 @@ import (
type cmdDeleteKey struct {
Positional struct {
- KeyName string `positional-arg-name:"<key-name>" description:"name of key to delete"`
+ KeyName string
} `positional-args:"true" required:"true"`
}
@@ -38,7 +38,10 @@ func init() {
i18n.G("Delete the local cryptographic key pair with the given name."),
func() flags.Commander {
return &cmdDeleteKey{}
- })
+ }, nil, []argDesc{{
+ name: i18n.G("<key-name>"),
+ desc: i18n.G("name of key to delete"),
+ }})
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_disconnect.go b/cmd/snap/cmd_disconnect.go
index 447bef1559..3c40ccbe43 100644
--- a/cmd/snap/cmd_disconnect.go
+++ b/cmd/snap/cmd_disconnect.go
@@ -27,8 +27,8 @@ import (
type cmdDisconnect struct {
Positionals struct {
- Offer SnapAndName `positional-arg-name:"<snap>:<plug>" required:"true"`
- Use SnapAndName `positional-arg-name:"<snap>:<slot>"`
+ Offer SnapAndName `required:"true"`
+ Use SnapAndName
} `positional-args:"true"`
}
@@ -53,6 +53,9 @@ Disconnects all plugs from the provided snap.
func init() {
addCommand("disconnect", shortDisconnectHelp, longDisconnectHelp, func() flags.Commander {
return &cmdDisconnect{}
+ }, nil, []argDesc{
+ {name: i18n.G("<snap>:<plug>")},
+ {name: i18n.G("<snap>:<slot>")},
})
}
diff --git a/cmd/snap/cmd_download.go b/cmd/snap/cmd_download.go
index 2838e025f1..3a9d8b5885 100644
--- a/cmd/snap/cmd_download.go
+++ b/cmd/snap/cmd_download.go
@@ -36,10 +36,10 @@ import (
type cmdDownload struct {
channelMixin
- Revision string `long:"revision" description:"Download the given revision of a snap, to which you must have developer access"`
+ Revision string `long:"revision"`
Positional struct {
- Snap string `positional-arg-name:"<snap>" description:"snap name"`
+ Snap string
} `positional-args:"true" required:"true"`
}
@@ -51,7 +51,12 @@ The download command will download the given snap and its supporting assertions
func init() {
addCommand("download", shortDownloadHelp, longDownloadHelp, func() flags.Commander {
return &cmdDownload{}
- })
+ }, channelDescs.also(map[string]string{
+ "revision": i18n.G("Download the given revision of a snap, to which you must have developer access"),
+ }), []argDesc{{
+ name: "<snap>",
+ desc: i18n.G("snap name"),
+ }})
}
func fetchSnapAssertions(sto *store.Store, snapPath string, snapInfo *snap.Info, dlOpts *image.DownloadOptions) error {
diff --git a/cmd/snap/cmd_export_key.go b/cmd/snap/cmd_export_key.go
index 9c6f457647..60273b425a 100644
--- a/cmd/snap/cmd_export_key.go
+++ b/cmd/snap/cmd_export_key.go
@@ -30,9 +30,9 @@ import (
)
type cmdExportKey struct {
- Account string `long:"account" description:"format public key material as a request for an account-key for this account-id"`
+ Account string `long:"account"`
Positional struct {
- KeyName string `positional-arg-name:"<key-name>" description:"name of key to export"`
+ KeyName string
} `positional-args:"true"`
}
@@ -42,7 +42,12 @@ func init() {
i18n.G("Export a public key assertion body that may be imported by other systems."),
func() flags.Commander {
return &cmdExportKey{}
- })
+ }, map[string]string{
+ "account": i18n.G("format public key material as a request for an account-key for this account-id"),
+ }, []argDesc{{
+ name: i18n.G("<key-name>"),
+ desc: i18n.G("name of key to export"),
+ }})
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_find.go b/cmd/snap/cmd_find.go
index bb93e2499f..ebadf74c97 100644
--- a/cmd/snap/cmd_find.go
+++ b/cmd/snap/cmd_find.go
@@ -85,16 +85,18 @@ func getPriceString(prices map[string]float64, suggestedCurrency, status string)
}
type cmdFind struct {
- Private bool `long:"private" description:"search private snaps"`
+ Private bool `long:"private"`
Positional struct {
- Query string `positional-arg-name:"<query>"`
+ Query string
} `positional-args:"yes"`
}
func init() {
addCommand("find", shortFindHelp, longFindHelp, func() flags.Commander {
return &cmdFind{}
- })
+ }, map[string]string{
+ "private": i18n.G("search private snaps"),
+ }, []argDesc{{name: i18n.G("<query>")}})
}
func (x *cmdFind) Execute(args []string) error {
diff --git a/cmd/snap/cmd_first_boot.go b/cmd/snap/cmd_first_boot.go
index 4064aefe24..fb6eede034 100644
--- a/cmd/snap/cmd_first_boot.go
+++ b/cmd/snap/cmd_first_boot.go
@@ -32,7 +32,7 @@ func init() {
"internal",
"internal", func() flags.Commander {
return &cmdInternalFirstBoot{}
- })
+ }, nil, nil)
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_get.go b/cmd/snap/cmd_get.go
index bdc4e3ceba..7d6875d9e4 100644
--- a/cmd/snap/cmd_get.go
+++ b/cmd/snap/cmd_get.go
@@ -35,15 +35,27 @@ The get command prints the configuration for the given snap.`)
type cmdGet struct {
Positional struct {
- Snap string `positional-arg-name:"<snap name>" description:"the snap whose conf is being requested"`
- Keys []string `positional-arg-name:"<keys>" description:"key of interest within the confuration"`
+ Snap string
+ Keys []string
} `positional-args:"yes" required:"yes"`
- Document bool `short:"d" description:"always return document, even with single key"`
+ Document bool `short:"d"`
}
func init() {
- addCommand("get", shortGetHelp, longGetHelp, func() flags.Commander { return &cmdGet{} })
+ addCommand("get", shortGetHelp, longGetHelp, func() flags.Commander { return &cmdGet{} },
+ map[string]string{
+ "d": i18n.G("always return document, even with single key"),
+ }, []argDesc{
+ {
+ name: "<snap>",
+ desc: i18n.G("the snap whose conf is being requested"),
+ },
+ {
+ name: i18n.G("<key>"),
+ desc: i18n.G("key of interest within the configuration"),
+ },
+ })
}
func (x *cmdGet) Execute(args []string) error {
diff --git a/cmd/snap/cmd_help.go b/cmd/snap/cmd_help.go
index 8470db02d6..033c91244e 100644
--- a/cmd/snap/cmd_help.go
+++ b/cmd/snap/cmd_help.go
@@ -32,12 +32,13 @@ var longHelpHelp = i18n.G(`
How help for the snap command.`)
type cmdHelp struct {
- Manpage bool `long:"man" description:"Generate the manpage"`
+ Manpage bool `long:"man"`
parser *flags.Parser
}
func init() {
- addCommand("help", shortHelpHelp, longHelpHelp, func() flags.Commander { return &cmdHelp{} })
+ addCommand("help", shortHelpHelp, longHelpHelp, func() flags.Commander { return &cmdHelp{} },
+ map[string]string{"man": i18n.G("Generate the manpage")}, nil)
}
func (cmd *cmdHelp) setParser(parser *flags.Parser) {
diff --git a/cmd/snap/cmd_interfaces.go b/cmd/snap/cmd_interfaces.go
index 7b3b1df9ca..af3f3ce6eb 100644
--- a/cmd/snap/cmd_interfaces.go
+++ b/cmd/snap/cmd_interfaces.go
@@ -28,9 +28,9 @@ import (
)
type cmdInterfaces struct {
- Interface string `short:"i" description:"constrain listing to specific interfaces"`
+ Interface string `short:"i"`
Positionals struct {
- Query SnapAndName `positional-arg-name:"<snap>:<slot or plug>" description:"snap or snap:name" skip-help:"true"`
+ Query SnapAndName `skip-help:"true"`
} `positional-args:"true"`
}
@@ -56,7 +56,12 @@ Filters the complete output so only plugs and/or slots matching the provided det
func init() {
addCommand("interfaces", shortInterfacesHelp, longInterfacesHelp, func() flags.Commander {
return &cmdInterfaces{}
- })
+ }, map[string]string{
+ "i": i18n.G("constrain listing to specific interfaces"),
+ }, []argDesc{{
+ name: i18n.G("<snap>:<slot or plug>"),
+ desc: i18n.G("snap or snap:name"),
+ }})
}
func (x *cmdInterfaces) Execute(args []string) error {
diff --git a/cmd/snap/cmd_keys.go b/cmd/snap/cmd_keys.go
index aea0c7e6ba..8781ef78b5 100644
--- a/cmd/snap/cmd_keys.go
+++ b/cmd/snap/cmd_keys.go
@@ -30,7 +30,7 @@ import (
)
type cmdKeys struct {
- JSON bool `long:"json" description:"output results in JSON format"`
+ JSON bool `long:"json"`
}
func init() {
@@ -39,7 +39,7 @@ func init() {
i18n.G("List cryptographic keys that can be used for signing assertions."),
func() flags.Commander {
return &cmdKeys{}
- })
+ }, map[string]string{"json": i18n.G("output results in JSON format")}, nil)
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_known.go b/cmd/snap/cmd_known.go
index 54e49c1fb0..f72cf7b6f8 100644
--- a/cmd/snap/cmd_known.go
+++ b/cmd/snap/cmd_known.go
@@ -31,8 +31,8 @@ import (
type cmdKnown struct {
KnownOptions struct {
- AssertTypeName string `positional-arg-name:"<assertion type>" description:"assertion type name" required:"true"`
- HeaderFilters []string `positional-arg-name:"<header filters>" description:"header=value" required:"0"`
+ AssertTypeName string `required:"true"`
+ HeaderFilters []string `required:"0"`
} `positional-args:"true" required:"true"`
}
@@ -46,6 +46,14 @@ shown must also have the specified headers matching the provided values.
func init() {
addCommand("known", shortKnownHelp, longKnownHelp, func() flags.Commander {
return &cmdKnown{}
+ }, nil, []argDesc{
+ {
+ name: i18n.G("<assertion type>"),
+ desc: i18n.G("assertion type name"),
+ }, {
+ name: i18n.G("<header filter>"),
+ desc: i18n.G("header=value"),
+ },
})
}
diff --git a/cmd/snap/cmd_list.go b/cmd/snap/cmd_list.go
index 2617abc5ec..2df8accca6 100644
--- a/cmd/snap/cmd_list.go
+++ b/cmd/snap/cmd_list.go
@@ -42,7 +42,7 @@ type cmdList struct {
}
func init() {
- addCommand("list", shortListHelp, longListHelp, func() flags.Commander { return &cmdList{} })
+ addCommand("list", shortListHelp, longListHelp, func() flags.Commander { return &cmdList{} }, nil, nil)
}
type snapsByName []*client.Snap
diff --git a/cmd/snap/cmd_login.go b/cmd/snap/cmd_login.go
index 2d5bfa2dc6..fd63f38bd7 100644
--- a/cmd/snap/cmd_login.go
+++ b/cmd/snap/cmd_login.go
@@ -32,9 +32,7 @@ import (
type cmdLogin struct {
Positional struct {
- // FIXME: add support for translated descriptions
- // (see cmd/snappy/common.go:addOptionDescription)
- UserName string `positional-arg-name:"email" description:"login.ubuntu.com email to login as"`
+ UserName string
} `positional-args:"yes" required:"yes"`
}
@@ -56,7 +54,11 @@ func init() {
longLoginHelp,
func() flags.Commander {
return &cmdLogin{}
- })
+ }, nil, []argDesc{{
+ // TRANSLATORS: noun
+ name: i18n.G("<email>"),
+ desc: i18n.G("login.ubuntu.com email to login as"),
+ }})
}
func requestLoginWith2faRetry(username, password string) error {
diff --git a/cmd/snap/cmd_logout.go b/cmd/snap/cmd_logout.go
index 71298cb799..993012cb33 100644
--- a/cmd/snap/cmd_logout.go
+++ b/cmd/snap/cmd_logout.go
@@ -37,7 +37,7 @@ func init() {
longLogoutHelp,
func() flags.Commander {
return &cmdLogout{}
- })
+ }, nil, nil)
}
func (cmd *cmdLogout) Execute(args []string) error {
diff --git a/cmd/snap/cmd_prepare_image.go b/cmd/snap/cmd_prepare_image.go
index b1341377f5..f138076e91 100644
--- a/cmd/snap/cmd_prepare_image.go
+++ b/cmd/snap/cmd_prepare_image.go
@@ -30,12 +30,12 @@ import (
type cmdPrepareImage struct {
Positional struct {
- ModelAssertionFn string `positional-arg-name:"model-assertion" description:"the model assertion name"`
- Rootdir string `long:"root-dir" description:"the output directory" `
+ ModelAssertionFn string
+ Rootdir string
} `positional-args:"yes" required:"yes"`
- ExtraSnaps []string `long:"extra-snaps" description:"extra snaps to be installed"`
- Channel string `long:"channel" description:"the channel to use"`
+ ExtraSnaps []string `long:"extra-snaps"`
+ Channel string `long:"channel"`
}
func init() {
@@ -44,6 +44,17 @@ func init() {
i18n.G("Prepare a snappy image"),
func() flags.Commander {
return &cmdPrepareImage{}
+ }, map[string]string{
+ "extra-snaps": "extra snaps to be installed",
+ "channel": "the channel to use",
+ }, []argDesc{
+ {
+ name: i18n.G("<model-assertion>"),
+ desc: i18n.G("the model assertion name"),
+ }, {
+ name: i18n.G("<root-dir>"),
+ desc: i18n.G("the output directory"),
+ },
})
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_run.go b/cmd/snap/cmd_run.go
index f43ef5a6a5..fe76fd0d0a 100644
--- a/cmd/snap/cmd_run.go
+++ b/cmd/snap/cmd_run.go
@@ -40,10 +40,10 @@ var (
)
type cmdRun struct {
- Command string `long:"command" description:"alternative command to run" hidden:"yes"`
- Hook string `long:"hook" description:"hook to run" hidden:"yes"`
- Revision string `short:"r" description:"use a specific snap revision when running hook" default:"unset" hidden:"yes"`
- Shell bool `long:"shell" description:"run a shell instead of the command (useful for debugging)"`
+ Command string `long:"command" hidden:"yes"`
+ Hook string `long:"hook" hidden:"yes"`
+ Revision string `short:"r" default:"unset" hidden:"yes"`
+ Shell bool `long:"shell" `
}
func init() {
@@ -52,7 +52,12 @@ func init() {
i18n.G("Run the given snap command with the right confinement and environment"),
func() flags.Commander {
return &cmdRun{}
- })
+ }, map[string]string{
+ "command": i18n.G("alternative command to run"),
+ "hook": i18n.G("hook to run"),
+ "r": i18n.G("use a specific snap revision when running hook"),
+ "shell": i18n.G("run a shell instead of the command (useful for debugging)"),
+ }, nil)
}
func (x *cmdRun) Execute(args []string) error {
diff --git a/cmd/snap/cmd_set.go b/cmd/snap/cmd_set.go
index f3762f3428..44e3ab04d7 100644
--- a/cmd/snap/cmd_set.go
+++ b/cmd/snap/cmd_set.go
@@ -36,13 +36,21 @@ accepts a number of key=value pairs of parameters.`)
type cmdSet struct {
Positional struct {
- Snap string `positional-arg-name:"<snap name>" description:"the snap to configure (e.g. hello-world)"`
- ConfValues []string `positional-arg-name:"<conf value>" description:"configuration value (key=value)" required:"1"`
+ Snap string
+ ConfValues []string `required:"1"`
} `positional-args:"yes" required:"yes"`
}
func init() {
- addCommand("set", shortSetHelp, longSetHelp, func() flags.Commander { return &cmdSet{} })
+ addCommand("set", shortSetHelp, longSetHelp, func() flags.Commander { return &cmdSet{} }, nil, []argDesc{
+ {
+ name: "<snap>",
+ desc: i18n.G("the snap to configure (e.g. hello-world)"),
+ }, {
+ name: i18n.G("<conf value>"),
+ desc: i18n.G("configuration value (key=value)"),
+ },
+ })
}
func (x *cmdSet) Execute(args []string) error {
diff --git a/cmd/snap/cmd_shell.go b/cmd/snap/cmd_shell.go
index 37c773c926..25578aacdc 100644
--- a/cmd/snap/cmd_shell.go
+++ b/cmd/snap/cmd_shell.go
@@ -32,7 +32,7 @@ import (
type cmdShell struct {
Positional struct {
- ShellType string `positional-arg-name:"shell-type" description:"The type of shell you want"`
+ ShellType string
} `positional-args:"yes"`
}
@@ -44,7 +44,10 @@ func init() {
i18n.G("Run snappy shell interface"),
func() flags.Commander {
return &cmdShell{}
- })
+ }, nil, []argDesc{{
+ name: i18n.G("<shell-type>"),
+ desc: i18n.G("The type of shell you want"),
+ }})
}
*/
diff --git a/cmd/snap/cmd_sign.go b/cmd/snap/cmd_sign.go
index 97811251cb..cc425c1f27 100644
--- a/cmd/snap/cmd_sign.go
+++ b/cmd/snap/cmd_sign.go
@@ -35,13 +35,13 @@ var longSignHelp = i18n.G(`Sign an assertion using the specified key, using the
`)
type cmdSign struct {
- KeyName string `short:"k" description:"name of the key to use, otherwise use the default key" default:"default"`
+ KeyName string `short:"k" default:"default"`
}
func init() {
cmd := addCommand("sign", shortSignHelp, longSignHelp, func() flags.Commander {
return &cmdSign{}
- })
+ }, map[string]string{"k": i18n.G("name of the key to use, otherwise use the default key")}, nil)
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_sign_build.go b/cmd/snap/cmd_sign_build.go
index 7e2b1fc18f..d62c291652 100644
--- a/cmd/snap/cmd_sign_build.go
+++ b/cmd/snap/cmd_sign_build.go
@@ -33,13 +33,13 @@ import (
type cmdSignBuild struct {
Positional struct {
- Filename string `positional-arg-name:"<filename>" description:"filename of the snap you want to assert a build for"`
+ Filename string
} `positional-args:"yes" required:"yes"`
- DeveloperID string `long:"developer-id" description:"identifier of the signer" required:"yes"`
- SnapID string `long:"snap-id" description:"identifier of the snap package associated with the build" required:"yes"`
- KeyName string `short:"k" default:"default" description:"name of the GnuPG key to use (defaults to 'default' as key name)"`
- Grade string `long:"grade" choice:"devel" choice:"stable" default:"stable" description:"grade states the build quality of the snap (defaults to 'stable')"`
+ DeveloperID string `long:"developer-id" required:"yes"`
+ SnapID string `long:"snap-id" required:"yes"`
+ KeyName string `short:"k" default:"default" `
+ Grade string `long:"grade" choice:"devel" choice:"stable" default:"stable"`
}
var shortSignBuildHelp = i18n.G("Create snap build assertion")
@@ -51,7 +51,15 @@ func init() {
longSignBuildHelp,
func() flags.Commander {
return &cmdSignBuild{}
- })
+ }, map[string]string{
+ "developer-id": i18n.G("identifier of the signer"),
+ "snap-id": i18n.G("identifier of the snap package associated with the build"),
+ "k": i18n.G("name of the GnuPG key to use (defaults to 'default' as key name)"),
+ "grade": i18n.G("grade states the build quality of the snap (defaults to 'stable')"),
+ }, []argDesc{{
+ name: i18n.G("<filename>"),
+ desc: i18n.G("filename of the snap you want to assert a build for"),
+ }})
cmd.hidden = true
}
diff --git a/cmd/snap/cmd_snap_op.go b/cmd/snap/cmd_snap_op.go
index 0dbb796cfe..68482d44a5 100644
--- a/cmd/snap/cmd_snap_op.go
+++ b/cmd/snap/cmd_snap_op.go
@@ -159,7 +159,7 @@ and the snap can easily be enabled again.
`)
type cmdRemove struct {
- Revision string `long:"revision" description:"Remove only the given revision"`
+ Revision string `long:"revision"`
Positional struct {
Snap string `positional-arg-name:"<snap>"`
} `positional-args:"yes" required:"yes"`
@@ -182,13 +182,34 @@ func (x *cmdRemove) Execute([]string) error {
}
type channelMixin struct {
- Channel string `long:"channel" description:"Use this channel instead of stable"`
+ Channel string `long:"channel"`
// shortcuts
- EdgeChannel bool `long:"edge" description:"Install from the edge channel"`
- BetaChannel bool `long:"beta" description:"Install from the beta channel"`
- CandidateChannel bool `long:"candidate" description:"Install from the candidate channel"`
- StableChannel bool `long:"stable" description:"Install from the stable channel"`
+ EdgeChannel bool `long:"edge"`
+ BetaChannel bool `long:"beta"`
+ CandidateChannel bool `long:"candidate"`
+ StableChannel bool `long:"stable" `
+}
+
+type mixinDescs map[string]string
+
+func (mxd mixinDescs) also(m map[string]string) mixinDescs {
+ n := make(map[string]string, len(mxd)+len(m))
+ for k, v := range mxd {
+ n[k] = v
+ }
+ for k, v := range m {
+ n[k] = v
+ }
+ return n
+}
+
+var channelDescs = mixinDescs{
+ "channel": i18n.G("Use this channel instead of stable"),
+ "beta": i18n.G("Install from the beta channel"),
+ "edge": i18n.G("Install from the edge channel"),
+ "candidate": i18n.G("Install from the candidate channel"),
+ "stable": i18n.G("Install from the stable channel"),
}
func (mx *channelMixin) setChannelFromCommandline() error {
@@ -251,8 +272,13 @@ func (mx *channelMixin) asksForChannel() bool {
}
type modeMixin struct {
- DevMode bool `long:"devmode" description:"Request non-enforcing security"`
- JailMode bool `long:"jailmode" description:"Override a snap's request for non-enforcing security"`
+ DevMode bool `long:"devmode"`
+ JailMode bool `long:"jailmode"`
+}
+
+var modeDescs = mixinDescs{
+ "devmode": i18n.G("Request non-enforcing security"),
+ "jailmode": i18n.G("Override a snap's request for non-enforcing security"),
}
var errModeConflict = errors.New(i18n.G("cannot use devmode and jailmode flags together"))
@@ -271,9 +297,9 @@ func (mx modeMixin) asksForMode() bool {
type cmdInstall struct {
channelMixin
modeMixin
- Revision string `long:"revision" description:"Install the given revision of a snap, to which you must have developer access"`
+ Revision string `long:"revision"`
- Dangerous bool `long:"dangerous" description:"Install the given snap file even if there are no pre-acknowledged signatures for it, meaning it was not verified and could be dangerous (--devmode implies this)"`
+ Dangerous bool `long:"dangerous"`
Positional struct {
Snap string `positional-arg-name:"<snap>"`
@@ -327,7 +353,7 @@ type cmdRefresh struct {
channelMixin
modeMixin
- List bool `long:"list" description:"show available snaps for refresh"`
+ List bool `long:"list"`
Positional struct {
Snaps []string `positional-arg-name:"<snap>"`
} `positional-args:"yes"`
@@ -591,11 +617,19 @@ func (x *cmdRevert) Execute(args []string) error {
}
func init() {
- addCommand("remove", shortRemoveHelp, longRemoveHelp, func() flags.Commander { return &cmdRemove{} })
- addCommand("install", shortInstallHelp, longInstallHelp, func() flags.Commander { return &cmdInstall{} })
- addCommand("refresh", shortRefreshHelp, longRefreshHelp, func() flags.Commander { return &cmdRefresh{} })
- addCommand("try", shortTryHelp, longTryHelp, func() flags.Commander { return &cmdTry{} })
- addCommand("enable", shortEnableHelp, longEnableHelp, func() flags.Commander { return &cmdEnable{} })
- addCommand("disable", shortDisableHelp, longDisableHelp, func() flags.Commander { return &cmdDisable{} })
- addCommand("revert", shortRevertHelp, longRevertHelp, func() flags.Commander { return &cmdRevert{} })
+ addCommand("remove", shortRemoveHelp, longRemoveHelp, func() flags.Commander { return &cmdRemove{} },
+ map[string]string{"revision": i18n.G("Remove only the given revision")}, nil)
+ addCommand("install", shortInstallHelp, longInstallHelp, func() flags.Commander { return &cmdInstall{} },
+ channelDescs.also(modeDescs).also(map[string]string{
+ "revision": i18n.G("Install the given revision of a snap, to which you must have developer access"),
+ "dangerous": i18n.G("Install the given snap file even if there are no pre-acknowledged signatures for it, meaning it was not verified and could be dangerous (--devmode implies this)"),
+ }), nil)
+ addCommand("refresh", shortRefreshHelp, longRefreshHelp, func() flags.Commander { return &cmdRefresh{} },
+ channelDescs.also(modeDescs).also(map[string]string{
+ "list": i18n.G("show available snaps for refresh"),
+ }), nil)
+ addCommand("try", shortTryHelp, longTryHelp, func() flags.Commander { return &cmdTry{} }, modeDescs, nil)
+ addCommand("enable", shortEnableHelp, longEnableHelp, func() flags.Commander { return &cmdEnable{} }, nil, nil)
+ addCommand("disable", shortDisableHelp, longDisableHelp, func() flags.Commander { return &cmdDisable{} }, nil, nil)
+ addCommand("revert", shortRevertHelp, longRevertHelp, func() flags.Commander { return &cmdRevert{} }, nil, nil)
}
diff --git a/cmd/snap/main.go b/cmd/snap/main.go
index 5f6841e768..dee4ed25f3 100644
--- a/cmd/snap/main.go
+++ b/cmd/snap/main.go
@@ -48,6 +48,11 @@ type options struct {
Version func() `long:"version" description:"print the version and exit"`
}
+type argDesc struct {
+ name string
+ desc string
+}
+
var optionsData options
// ErrExtraArgs is returned if extra arguments to a command are found
@@ -58,6 +63,8 @@ type cmdInfo struct {
name, shortHelp, longHelp string
builder func() flags.Commander
hidden bool
+ optDescs map[string]string
+ argDescs []argDesc
}
// commands holds information about all non-experimental commands.
@@ -68,12 +75,14 @@ var experimentalCommands []*cmdInfo
// addCommand replaces parser.addCommand() in a way that is compatible with
// re-constructing a pristine parser.
-func addCommand(name, shortHelp, longHelp string, builder func() flags.Commander) *cmdInfo {
+func addCommand(name, shortHelp, longHelp string, builder func() flags.Commander, optDescs map[string]string, argDescs []argDesc) *cmdInfo {
info := &cmdInfo{
name: name,
shortHelp: shortHelp,
longHelp: longHelp,
builder: builder,
+ optDescs: optDescs,
+ argDescs: argDescs,
}
commands = append(commands, info)
return info
@@ -143,6 +152,34 @@ The snap tool interacts with the snapd daemon to control the snappy software pla
logger.Panicf("cannot add command %q: %v", c.name, err)
}
cmd.Hidden = c.hidden
+
+ if c.optDescs != nil {
+ opts := cmd.Options()
+ if len(opts) != len(c.optDescs) {
+ logger.Panicf("wrong number of option descriptions for %s: expected %d, got %d", c.name, len(opts), len(c.optDescs))
+ }
+ for _, opt := range opts {
+ desc, ok := c.optDescs[opt.LongName]
+ if !ok {
+ desc, ok = c.optDescs[string(opt.ShortName)]
+ if !ok {
+ logger.Panicf("%s missing description for %s", c.name, opt)
+ }
+ }
+ opt.Description = desc
+ }
+ }
+
+ if c.argDescs != nil {
+ args := cmd.Args()
+ if len(args) != len(c.argDescs) {
+ logger.Panicf("wrong number of argument descriptions for %s: expected %d, got %d", c.name, len(args), len(c.argDescs))
+ }
+ for i, arg := range args {
+ arg.Name = c.argDescs[i].name
+ arg.Description = c.argDescs[i].desc
+ }
+ }
}
// Add the experimental command
experimentalCommand, err := parser.AddCommand("experimental", shortExperimentalHelp, longExperimentalHelp, &cmdExperimental{})
diff --git a/cmd/snap/notes.go b/cmd/snap/notes.go
index 74849dc64a..0801b3acb4 100644
--- a/cmd/snap/notes.go
+++ b/cmd/snap/notes.go
@@ -21,6 +21,8 @@ package main
import (
"strings"
+
+ "github.com/snapcore/snapd/i18n"
)
// Notes encapsulate everything that might be interesting about a
@@ -51,7 +53,8 @@ func (n *Notes) String() string {
}
if n.Private {
- ns = append(ns, "private")
+ // TRANSLATORS: if possible, a single short word
+ ns = append(ns, i18n.G("private"))
}
if n.TryMode {
@@ -59,11 +62,13 @@ func (n *Notes) String() string {
}
if n.Disabled {
- ns = append(ns, "disabled")
+ // TRANSLATORS: if possible, a single short word
+ ns = append(ns, i18n.G("disabled"))
}
if n.Broken {
- ns = append(ns, "broken")
+ // TRANSLATORS: if possible, a single short word
+ ns = append(ns, i18n.G("broken"))
}
if len(ns) == 0 {
diff --git a/tests/lib/assertions/developer1.account-key b/tests/lib/assertions/developer1.account-key
index 57c256344d..302d42629a 100644
--- a/tests/lib/assertions/developer1.account-key
+++ b/tests/lib/assertions/developer1.account-key
@@ -2,8 +2,8 @@ type: account-key
authority-id: testrootorg
public-key-sha3-384: EAD4DbLxK_kn0gzNCXOs3kd6DeMU3f-L6BEsSEuJGBqCORR0gXkdDxMbOm11mRFu
account-id: developer1
+name: default
since: 2016-08-19T15:49:45+02:00
-timestamp: 2016-08-11T18:46:02+02:00
body-length: 717
sign-key-sha3-384: XCIC_Wvj9_hiAt0b10sDon74oGr3a6xGODkMZqrj63ZzNYUD5N87-ojjPoeN7f1Y
@@ -18,13 +18,13 @@ Yy04Sf9LI148vJMsYenonkoWejWdMi8iCUTeaZydHJEUBU/RbNFLjCWa6NIUe9bfZgLiOOZkps54
+/AL078ri/tGjo/5UGvezSmwrEoWJyqrJt2M69N2oVDLJcHeo2bUYPtFC2Kfb2je58JrJ+llifdg
rAsxbnHXiXyVimUAEQEAAQ==
-AcLBUgQAAQoABgUCV7cOeQAAFqUQAE90GSklMLMqwudOcx2Za0xVHoNdmcE4yMI4gMdtzMM+I0EY
-rosU5XtObDSWog+7u1ojExWAEm01KwP4QGYJunHPYOKWb/72hS1u/xj3e3EUx04cwQw/MfhS+C5i
-D1FPtHe/ikaFBiM5grHhGFbcPWCZhzzSsDCEVsy59UnS4AR4I0ADFvLGJUgSRr+FIf8EYatGAxib
-u8gWHBnxhfgZ/Z7CWDEyZkFmPGoJ7NsJBxXpJ3w/LPvEvFpT03zdn3abIDvXEq0zpwoPper2+xUD
-RDvJYrNswxs7nWsZKIwLazTw470US5nEYyQ9JoVXilWUXXY1HukILIyzB0kMV1ncPo4ru982K0rf
-0JPHV8qpOBn5vZyH/ZZMl9KarlQbkG+bYhFmugD83f7quF/AwFUe/oeMtj7BiDwEV5BxENoFu8SR
-wbuKUswCaWNGhwnJ8l4pNBg5mnkleLVUtOCVVxsWP84lvAGlj9zeQB8G17GbzdAf5SiSkWNq4tMV
-b1UIoWjYvPb4+GPgo5zaqXWaGeiU2onU1UCRNE3UxIHim8u88mWAtxV8PvKqRkQC9ecJLQtI0nD4
-ubwvQeWP793+3nU47um7Dfs7CFD9aLjsgvshGSaBW90GQpK2WNL9hR5aMCzuL8uXXaKpvRKMGM1X
-hlym2K7K9yVpIumDPAYV8rmF9hD3
+AcLBXAQAAQoABgUCV9AFCQAKCRDdoJRfvd5vjf8OD/4nfa6dj+39OyxfBJYXCUgFj2qCPYUm66j+
+bwNY6YD96ZP4QKx4+Vhqrmu2RWhUISqHhJH/SPKxfil9nocWE16knSgE4HFUnb2omMBIq+wU1ThV
+JFvxdWXt9KFMKBlYeKr1BOoXuPJlQGf8PnsTM1oCqrzAyfGAbSFmsVrqzf8ujyQf551f5RovdSLX
+dqEZX1tYiyoIY9FJNGL5A74Q8CC9V1yJEPtEcQDEovoa368fD2S3rvyBYtaHnQ3tkrvZgZCPe0lJ
+4FwtljgGuAALAQPcZOepJRnNP60/eeYJGZPlQwwjD7akxy/FrDIjbqW/HRxHcroyKHGnMSD+DX5D
+GPSxClb1ChI+RqyNZJzubIh11lnLmidYsonNjTYZRmMDPn/iIo2Goso8hYKoF8ztJeD42otdLrMN
+omV7qZvf2bHZ5ADUPsK468CNOPnjQYL0AC5D01C+QxrkPfK8aJQiK0/rYcgCouSetzJHl0JrC0Fv
+6IfwpPxXz0/KHqDwsC20yGhmyCS8RjqzTX1pkn/hPeHD7qNJBP+4oXfyGdaEu+vsiAWIkgasv3Um
+pePncPNiBxTBHJEyYeRK6w5uy2M9iUe/QLT/1cHEYgsQDDr2mSV8rdpV9pn5JcyfiCL0nhIMPbTU
+DcIT7mkb48ulFZpKyVYubC5yDPGfaK621eRS5UfO1Q==
diff --git a/tests/lib/assertions/testrootorg-store.account-key b/tests/lib/assertions/testrootorg-store.account-key
index f3b0827f60..d04be39745 100644
--- a/tests/lib/assertions/testrootorg-store.account-key
+++ b/tests/lib/assertions/testrootorg-store.account-key
@@ -2,6 +2,7 @@ type: account-key
authority-id: testrootorg
public-key-sha3-384: XCIC_Wvj9_hiAt0b10sDon74oGr3a6xGODkMZqrj63ZzNYUD5N87-ojjPoeN7f1Y
account-id: testrootorg
+name: test-store
since: 2016-08-11T18:42:22+02:00
body-length: 717
sign-key-sha3-384: hIedp1AvrWlcDI4uS_qjoFLzjKl5enu4G2FYJpgB3Pj-tUzGlTQBxMBsBmi-tnJR
@@ -17,13 +18,13 @@ iAIwA4DpGMmFJ26maqVzJuiLvicri2FR/sJaSA24N8HbGne3gSS7WrSQS+jKe3IZPVy64NCoGvrW
o/HvTeqsIfihKPEpXm8QVtjNhtkVn3RdIUgOaNWyAfnZ4dW1TVIATe+OHDw2TNyImTjE0x75nL6B
1/Rrn+9VP9Swhv8AEQEAAQ==
-AcLBUgQAAQoABgUCV6yq7gAAhaYQAMIVYhta2uUvm5PXApdXmZFWr+iZYfkAZW8PEMOsuYVHbDoH
-oA7dpO0EwZXl/mCgGjNNc4nUmqQLBiIwwrcnmcYSRl2Xz+u+ssou4YMueXOD2tHo1N2J39SKpS72
-VqQsnF77Qylgdp2j7Q9lJrU0qHz0M195OJXNSppfdHYeWptfsO02cApPobU9s6KT5VggVg1ushNM
-1u97A+uvoClfJ53PPafC0kr1+vwFVPj+mko4gc7sIB42xwz+YeR47CgSaT8i8K1u5ouaHCNxe61+
-siQxAdIOv+hOAWAOMoOWZjxh5K7J9A1Dc18EMyggf716IUCBtkvDKfwcXFcoic1X6EVPO5kNhlh7
-aLFS8UVsBPaZKnJm3ZFudhYQUmZt22ntslgGrq9+NBeh9i6nswUPKdj2idHkyQsjnYgYyTpEH/xh
-sBqkqkedPkUtn+tP5dS4TP/L3Xq/q2tQbA4+gNVbZXIo8g8wfpsP8+sIOnEV5UcoxaO3oI3YUJrN
-oFGmC7No802XKG1ZNHhBtMSaan0pafWrBrHn+axT9Jbl/1B73TYg3zQWOpDuSpH3SU+gXJ/eukUb
-LvC8UWZM9YEhu/ZINSvSDQzvobV/NVrHWEJBXxrc3CwseFDgQq+Yz/CFGud79z2mS8lNHKSqQdFK
-3Bd0HG1HheYcX0nGIva0KIG0Sgf/
+AcLBXAQAAQoABgUCV866kwAKCRBMcZp594FxpHWHD/9AaZXqyT/Zsmq/VzmAMpd9JvCH4PHQKtAP
+bXfP2Dnpa2wk2wuzQuSWunR8NDRyVh/aNVeTEZ9dFm/B8LR+U2O4rsHmFSeicmsTmo9u/HouRdEU
+zeSc6cbAxMPpfNSjr5J+URLjGRT6oX5fEBmRPx/OC9pEIScMx7uKmTKEnuyMzLRNN/6HiGWKrFCo
+nJdKkwRXrkCHyXWAOv1GumT7NDuyFcjAqt/UdHliTZkDBImKOsBmBVXMUjg7HCSS2uq/5WjStJ+B
+JHQ4GSsXBvVINs6BncNWcvV6mCQ73D57MzGhqo997Zb4tSrn7UNGWK7GLCzV3e/pFlG7pw6HbgnQ
++rxU2Oj/TPVw0tcnUiRl2ttKpm+nua0Cl+MD+Gx0KXLAVp0ZGOQ9yGyP9AePFzcOR8SlRIgxi0EI
+iJkSeYilqoKo3AJhnICRiqvAca2TGJoiJUryEgZ8jbTOElfaF2p+y0xvXGlWbKZm1gzGyvFM5fV5
+hJTlp/am+2uVn6U8wPACir4PrbuXYo7L4MIXww2OEO0ruBIaLARbc5IutSWmw6AEYQUxtsa9bdHV
+Zin7LGbEj6lZm8GycWQwh4B6Vnt6dJRIyPc/9G7uM8Ds/2Wa7+yAxhiPqm8DwlbOYh1npw4X4TLD
+IMGnTv5N3zllI+Xz4rqJzNTzEbvOIcrqWxCedQe79A==
diff --git a/tests/main/ack/developer1.account b/tests/main/ack/developer1.account
deleted file mode 100644
index 65e9c49180..0000000000
--- a/tests/main/ack/developer1.account
+++ /dev/null
@@ -1,19 +0,0 @@
-type: account
-authority-id: testrootorg
-account-id: developer1
-display-name: Developer 1
-timestamp: 2016-08-11T18:46:02+02:00
-username: developer1
-validation: unproven
-sign-key-sha3-384: XCIC_Wvj9_hiAt0b10sDon74oGr3a6xGODkMZqrj63ZzNYUD5N87-ojjPoeN7f1Y
-
-AcLBUgQAAQoABgUCV6yrygAAf0IQAFhUje+lWt6c2bmjUg17vo3tdowkpNYvXLma0ajCphULCmGg
-3qtS8sVCJa1rFXOeKr3whFc/iheFbZJyQh2lsIQsRE0Z587uxFLbs5ua0FU0yzU7va03PdfBIK9o
-BEC8uXdCx3yFlijnDibC3D6daqB/PkUUM+WT5ypvp4b4l/IY6Vf0s2p5IzYCToBcXzdXOuL6e8t8
-d2kC2q1P5WX+fK20UjSeGHSanR6sfr4Tw+FNgv/MtmaBAkhAbIHMKTOZaKd7sEjT0QLJVYRdWlTp
-dSpaJRqzTE2v7Ql7RjJtFO8+QKnjzNGMbRYj9yX9meBBeT+iDTqH4UrvyRBOmLlKVAkt8mXqjwWj
-IfuA+ISWb8Qc/aah/DO6wONt7oAD6AkXXrCFtinHyQCutD5/XB63eCeSJAfvxD2Nbx9xEWjwu4nE
-6D6a5URpU4Kzo05jk/OnCnjMHYgW/grn9wRt4yXAXWkENQfcAYH6ZZV4VTJqA+2fAO7q9v5WryA4
-z1WcX7073ORAFCviiUFEoX/3eUfsyom5AF45OKxs8bm8jfRemnfUjRrPbpV+auBZ+DG7NDKFAOVv
-zKKivSNac125adAcC3Xg1783eUMpDStylIOxziVYDB0e6nR4ekiccnvLv5GpiEOG0ZGa4sUYI6+V
-hWeNg9t8ydjCleMJcHt5WrgcBQuw
diff --git a/tests/main/ack/task.yaml b/tests/main/ack/task.yaml
index e4fc41af1b..b655ae3b26 100644
--- a/tests/main/ack/task.yaml
+++ b/tests/main/ack/task.yaml
@@ -11,14 +11,14 @@ prepare: |
systemctl start snapd.socket snapd.service
execute: |
echo "Ack when missing matching key fails"
- ! snap ack developer1.account
+ ! snap ack cp $TESTSLIB/assertions/developer1.account
echo "Ack the test store key"
- snap ack testrootorg-store.account-key
+ snap ack $TESTSLIB/assertions/testrootorg-store.account-key
snap known account-key public-key-sha3-384=XCIC_Wvj9_hiAt0b10sDon74oGr3a6xGODkMZqrj63ZzNYUD5N87-ojjPoeN7f1Y | grep "^sign-key-sha3-384: hIedp1AvrWlcDI4uS_qjoFLzjKl5enu4G2FYJpgB3Pj-tUzGlTQBxMBsBmi-tnJR$"
echo "Ack a developer account signed by that"
- snap ack developer1.account
+ snap ack $TESTSLIB/assertions/developer1.account
snap known account account-id=developer1|grep "^username: developer1$"
diff --git a/tests/main/ack/testrootorg-store.account-key b/tests/main/ack/testrootorg-store.account-key
deleted file mode 100644
index f3b0827f60..0000000000
--- a/tests/main/ack/testrootorg-store.account-key
+++ /dev/null
@@ -1,29 +0,0 @@
-type: account-key
-authority-id: testrootorg
-public-key-sha3-384: XCIC_Wvj9_hiAt0b10sDon74oGr3a6xGODkMZqrj63ZzNYUD5N87-ojjPoeN7f1Y
-account-id: testrootorg
-since: 2016-08-11T18:42:22+02:00
-body-length: 717
-sign-key-sha3-384: hIedp1AvrWlcDI4uS_qjoFLzjKl5enu4G2FYJpgB3Pj-tUzGlTQBxMBsBmi-tnJR
-
-AcbBTQRWhcGAARAAmJqmZvsS58INTs+UQ+jfo836vBS5tkU/hk7c0huZe3So2gc9kaJvjkjhZ6g0
-0/kGoidw3i2WkdMEp+JtvU9Ztfeu/Nn/OqkSc3Ap1KAmqL4OllPVII8H69w2zqvmo+PcqH0SvHAV
-EoOC2ToXP0wEHnAZsbVu56AKrwpHDppPEIvaS6glrsEX1AXpOeMHZLVRtfsBB6dlLXuula1UrSAL
-RFCEjXtqXqOto5Vo9C8p63lLBy19ifz4OriWAGBqZvFdItmo8VIPXSDdgMMHBlu2MSNJHVfo66yp
-buos5Qs6PlVARACLJgzgplI5sDbzXtVx5O9Q8YJjz4NfU0WOWYPWvmANXDeMNixWoWvuixYvsXaG
-x6mdD7Hh/gi8prkQmZ7gxW1MEOV9JThAqYjjs2ayGVD73EI2sKYxVwEg3iJToQ/cEz3O2U1HdmYj
-QfRDJiX3GEPBXXttDrbPM42SHElouldmJ+PkJDLdkGmA85xYUoEKHdEFIkjFStQcyO5CkyNZN7SH
-iAIwA4DpGMmFJ26maqVzJuiLvicri2FR/sJaSA24N8HbGne3gSS7WrSQS+jKe3IZPVy64NCoGvrW
-o/HvTeqsIfihKPEpXm8QVtjNhtkVn3RdIUgOaNWyAfnZ4dW1TVIATe+OHDw2TNyImTjE0x75nL6B
-1/Rrn+9VP9Swhv8AEQEAAQ==
-
-AcLBUgQAAQoABgUCV6yq7gAAhaYQAMIVYhta2uUvm5PXApdXmZFWr+iZYfkAZW8PEMOsuYVHbDoH
-oA7dpO0EwZXl/mCgGjNNc4nUmqQLBiIwwrcnmcYSRl2Xz+u+ssou4YMueXOD2tHo1N2J39SKpS72
-VqQsnF77Qylgdp2j7Q9lJrU0qHz0M195OJXNSppfdHYeWptfsO02cApPobU9s6KT5VggVg1ushNM
-1u97A+uvoClfJ53PPafC0kr1+vwFVPj+mko4gc7sIB42xwz+YeR47CgSaT8i8K1u5ouaHCNxe61+
-siQxAdIOv+hOAWAOMoOWZjxh5K7J9A1Dc18EMyggf716IUCBtkvDKfwcXFcoic1X6EVPO5kNhlh7
-aLFS8UVsBPaZKnJm3ZFudhYQUmZt22ntslgGrq9+NBeh9i6nswUPKdj2idHkyQsjnYgYyTpEH/xh
-sBqkqkedPkUtn+tP5dS4TP/L3Xq/q2tQbA4+gNVbZXIo8g8wfpsP8+sIOnEV5UcoxaO3oI3YUJrN
-oFGmC7No802XKG1ZNHhBtMSaan0pafWrBrHn+axT9Jbl/1B73TYg3zQWOpDuSpH3SU+gXJ/eukUb
-LvC8UWZM9YEhu/ZINSvSDQzvobV/NVrHWEJBXxrc3CwseFDgQq+Yz/CFGud79z2mS8lNHKSqQdFK
-3Bd0HG1HheYcX0nGIva0KIG0Sgf/
diff --git a/tests/main/login/missing_email_error.exp b/tests/main/login/missing_email_error.exp
index 8470b39d95..4bd1e1ff2e 100644
--- a/tests/main/login/missing_email_error.exp
+++ b/tests/main/login/missing_email_error.exp
@@ -1,7 +1,7 @@
spawn snap login
expect {
- "required argument `email` was not provided" {
+ "required argument `<email>` was not provided" {
exit 0
} default {
exit 1