diff options
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 |
