diff --git a/.gitignore b/.gitignore index 25326c74cfc..0ead5a99360 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ integration-tests/data/output/ share tags .coverage +cmd/version_generated.go *~ diff --git a/client/client.go b/client/client.go index 23ddf39f9c1..c71e106c899 100644 --- a/client/client.go +++ b/client/client.go @@ -189,6 +189,20 @@ func (client *Client) doAsync(method, path string, query url.Values, headers map return rsp.Change, nil } +func (client *Client) ServerVersion() (string, error) { + sysInfo, err := client.SysInfo() + if err != nil { + return "unknown", err + } + + version := sysInfo.Version + if version == "" { + version = "unknown" + } + + return fmt.Sprintf("%s (series %s)", version, sysInfo.Series), nil +} + // A response produced by the REST API will usually fit in this // (exceptions are the icons/ endpoints obvs) type response struct { @@ -232,11 +246,8 @@ func IsTwoFactorError(err error) bool { // SysInfo holds system information type SysInfo struct { - Flavor string `json:"flavor"` - Release string `json:"release"` - DefaultChannel string `json:"default-channel"` - APICompatibility string `json:"api-compat"` - Store string `json:"store,omitempty"` + Series string `json:"series,omitempty"` + Version string `json:"version,omitempty"` } func (rsp *response) err() error { diff --git a/client/client_test.go b/client/client_test.go index 06d05f4b5d1..1a929ec76cb 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -78,23 +78,6 @@ func (cs *clientSuite) TestNewPanics(c *check.C) { client.New(&client.Config{BaseURL: ":"}) }, check.PanicMatches, `cannot parse server base URL: ":" \(parse :: missing protocol scheme\)`) } - -func (cs *clientSuite) TestNewCustomURL(c *check.C) { - f := func(w http.ResponseWriter, r *http.Request) { - c.Check(r.URL.Path, check.Equals, "/v2/system-info") - c.Check(r.URL.RawQuery, check.Equals, "") - fmt.Fprintln(w, `{"type":"sync", "result":{"store":"X"}}`) - } - srv := httptest.NewServer(http.HandlerFunc(f)) - defer srv.Close() - - cli := client.New(&client.Config{BaseURL: srv.URL}) - c.Assert(cli, check.Not(check.IsNil)) - si, err := cli.SysInfo() - c.Check(err, check.IsNil) - c.Check(si.Store, check.Equals, "X") -} - func (cs *clientSuite) TestClientDoReportsErrors(c *check.C) { cs.err = errors.New("ouchie") err := cs.cli.Do("GET", "/", nil, nil, nil) @@ -148,19 +131,13 @@ func (cs *clientSuite) TestClientSetsAuthorization(c *check.C) { func (cs *clientSuite) TestClientSysInfo(c *check.C) { cs.rsp = `{"type": "sync", "result": - {"flavor": "f", - "release": "r", - "default-channel": "dc", - "api-compat": "42", - "store": "store"}}` + {"series": "16", + "version": "2"}}` sysInfo, err := cs.cli.SysInfo() c.Check(err, check.IsNil) c.Check(sysInfo, check.DeepEquals, &client.SysInfo{ - Flavor: "f", - Release: "r", - DefaultChannel: "dc", - APICompatibility: "42", - Store: "store", + Version: "2", + Series: "16", }) } @@ -175,7 +152,7 @@ func (cs *clientSuite) TestClientIntegration(c *check.C) { c.Check(r.URL.Path, check.Equals, "/v2/system-info") c.Check(r.URL.RawQuery, check.Equals, "") - fmt.Fprintln(w, `{"type":"sync", "result":{"store":"X"}}`) + fmt.Fprintln(w, `{"type":"sync", "result":{"series":"42"}}`) } srv := &httptest.Server{ @@ -188,7 +165,7 @@ func (cs *clientSuite) TestClientIntegration(c *check.C) { cli := client.New(nil) si, err := cli.SysInfo() c.Check(err, check.IsNil) - c.Check(si.Store, check.Equals, "X") + c.Check(si.Series, check.Equals, "42") } func (cs *clientSuite) TestClientReportsOpError(c *check.C) { diff --git a/cmd/snap/cmd_connect_test.go b/cmd/snap/cmd_connect_test.go index ca954204464..1bc6258ac7b 100644 --- a/cmd/snap/cmd_connect_test.go +++ b/cmd/snap/cmd_connect_test.go @@ -53,6 +53,9 @@ the gadget snap, the kernel snap, and then the os snap, in that order. The first of these snaps that has a matching plug name is used and the command proceeds as above. +Application Options: + --version print the version and exit + Help Options: -h, --help Show this help message ` diff --git a/cmd/snap/cmd_disconnect_test.go b/cmd/snap/cmd_disconnect_test.go index acc52a63287..c13b750b83b 100644 --- a/cmd/snap/cmd_disconnect_test.go +++ b/cmd/snap/cmd_disconnect_test.go @@ -48,6 +48,9 @@ $ snap disconnect Disconnects all plugs from the provided snap. +Application Options: + --version print the version and exit + Help Options: -h, --help Show this help message ` diff --git a/cmd/snap/cmd_help_test.go b/cmd/snap/cmd_help_test.go index acd6cb894e2..0ec2d63f0c3 100644 --- a/cmd/snap/cmd_help_test.go +++ b/cmd/snap/cmd_help_test.go @@ -48,6 +48,9 @@ The snap tool interacts with the snapd daemon to control the snappy software platform. +Application Options: + +--version +print the version and exit + Help Options: +-h, --help +Show this help message diff --git a/cmd/snap/cmd_interfaces_test.go b/cmd/snap/cmd_interfaces_test.go index 92fd205474c..5446523751f 100644 --- a/cmd/snap/cmd_interfaces_test.go +++ b/cmd/snap/cmd_interfaces_test.go @@ -51,6 +51,9 @@ $ snap interfaces -i= [] Filters the complete output so only plugs and/or slots matching the provided details are listed. +Application Options: + --version print the version and exit + Help Options: -h, --help Show this help message diff --git a/cmd/snap/main.go b/cmd/snap/main.go index e6f14536ce3..bac78b4ec1c 100644 --- a/cmd/snap/main.go +++ b/cmd/snap/main.go @@ -26,6 +26,8 @@ import ( "strings" "github.com/ubuntu-core/snappy/client" + "github.com/ubuntu-core/snappy/cmd" + "github.com/ubuntu-core/snappy/i18n" "github.com/ubuntu-core/snappy/logger" "github.com/jessevdk/go-flags" @@ -39,7 +41,7 @@ var ( ) type options struct { - // No global options yet + Version func() `long:"version" description:"print the version and exit"` } var optionsData options @@ -92,6 +94,16 @@ type parserSetter interface { // Since commands have local state a fresh parser is required to isolate tests // from each other. func Parser() *flags.Parser { + optionsData.Version = func() { + cv, err := Client().ServerVersion() + if err != nil { + cv = i18n.G("unavailable") + } + + fmt.Fprintf(Stdout, "snap %s\nsnapd %s\n", cmd.Version, cv) + + os.Exit(0) + } parser := flags.NewParser(&optionsData, flags.HelpFlag|flags.PassDoubleDash) parser.ShortDescription = "Tool to interact with snaps" parser.LongDescription = ` diff --git a/cmd/snapd/main.go b/cmd/snapd/main.go index f7a9c4bd365..254b7a731dd 100644 --- a/cmd/snapd/main.go +++ b/cmd/snapd/main.go @@ -25,6 +25,7 @@ import ( "os/signal" "syscall" + "github.com/ubuntu-core/snappy/cmd" "github.com/ubuntu-core/snappy/daemon" "github.com/ubuntu-core/snappy/logger" ) @@ -48,10 +49,10 @@ func run() error { if err != nil { return err } - if err := d.Init(); err != nil { return err } + d.Version = cmd.Version d.Start() diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 00000000000..1b8cd74bd32 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,25 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2016 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package cmd + +//go:generate mkversion.sh + +// will be overwritten at build-time via mkversion.sh +var Version = "unknown" diff --git a/daemon/api.go b/daemon/api.go index 5f512f7b3ad..affee295bec 100644 --- a/daemon/api.go +++ b/daemon/api.go @@ -173,7 +173,8 @@ func tbd(c *Command, r *http.Request, user *auth.UserState) Response { func sysInfo(c *Command, r *http.Request, user *auth.UserState) Response { m := map[string]string{ - "series": release.Series, + "series": release.Series, + "version": c.d.Version, } return SyncResponse(m, nil) diff --git a/daemon/api_test.go b/daemon/api_test.go index 8805cf414bd..3d322997f46 100644 --- a/daemon/api_test.go +++ b/daemon/api_test.go @@ -397,31 +397,15 @@ func (s *apiSuite) TestSysInfo(c *check.C) { rec := httptest.NewRecorder() c.Check(sysInfoCmd.Path, check.Equals, "/v2/system-info") - sysInfoCmd.GET(sysInfoCmd, nil, nil).ServeHTTP(rec, nil) - c.Check(rec.Code, check.Equals, 200) - c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") - - expected := map[string]interface{}{ - "series": "16", - } - var rsp resp - c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) - c.Check(rsp.Status, check.Equals, 200) - c.Check(rsp.Type, check.Equals, ResponseTypeSync) - c.Check(rsp.Result, check.DeepEquals, expected) -} - -func (s *apiSuite) TestSysInfoStore(c *check.C) { - rec := httptest.NewRecorder() - c.Check(sysInfoCmd.Path, check.Equals, "/v2/system-info") - - s.mkGadget(c, "some-store") + s.daemon(c).Version = "42b1" sysInfoCmd.GET(sysInfoCmd, nil, nil).ServeHTTP(rec, nil) c.Check(rec.Code, check.Equals, 200) + c.Check(rec.HeaderMap.Get("Content-Type"), check.Equals, "application/json") expected := map[string]interface{}{ - "series": "16", + "series": "16", + "version": "42b1", } var rsp resp c.Assert(json.Unmarshal(rec.Body.Bytes(), &rsp), check.IsNil) diff --git a/daemon/daemon.go b/daemon/daemon.go index 32cf6c9f117..cb11d882517 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -40,6 +40,7 @@ import ( // A Daemon listens for requests and routes them to the right command type Daemon struct { + Version string overlord *overlord.Overlord listener net.Listener tomb tomb.Tomb diff --git a/debian/control b/debian/control index 19757083dc7..7e073ae05cc 100644 --- a/debian/control +++ b/debian/control @@ -4,7 +4,7 @@ Priority: optional Maintainer: Ubuntu Developers Build-Depends: bash-completion, debhelper (>= 9), - dh-golang, + dh-golang (>=1.7), dh-systemd, fakeroot, gettext, diff --git a/debian/rules b/debian/rules index 80fedd5a75e..63c81157b40 100755 --- a/debian/rules +++ b/debian/rules @@ -6,6 +6,12 @@ export DH_OPTIONS export DH_GOPKG := github.com/ubuntu-core/snappy #export DEB_BUILD_OPTIONS=nocheck export DH_GOLANG_EXCLUDES=integration-tests +export DH_GOLANG_GO_GENERATE=1 + +# we need the builddir; is there a simpler way to get it? +BUILDDIR:=${CURDIR}/obj-$(shell dpkg-architecture -qDEB_TARGET_GNU_TYPE) + +export PATH:=${PATH}:${CURDIR} RELEASE = $(shell lsb_release -c -s) @@ -54,18 +60,6 @@ override_dh_systemd_start: -psnapd \ snapd.socket -# we need the builddir; is there a simpler way to get it? -BUILDDIR:=${CURDIR}/obj-$(shell dpkg-architecture -qDEB_TARGET_GNU_TYPE) - -override_dh_auto_build: - dh_auto_build - # this will update the i18n stuff using our build-in xgettext-go - if [ "$(RELEASE)" = "vivid" ]; then\ - GOPATH=${BUILDDIR} ./update-pot;\ - else\ - GOPATH=${BUILDDIR} go generate ./i18n;\ - fi; - override_dh_auto_install: snap.8 dh_auto_install -O--buildsystem=golang # we do not need this in the package, its just needed during build diff --git a/i18n/i18n.go b/i18n/i18n.go index e960dd7ed99..75ca2ea3b1f 100644 --- a/i18n/i18n.go +++ b/i18n/i18n.go @@ -19,7 +19,7 @@ package i18n -//go:generate ../update-pot +//go:generate update-pot import ( "github.com/gosexy/gettext" diff --git a/mkversion.sh b/mkversion.sh new file mode 100755 index 00000000000..3abfbb759d7 --- /dev/null +++ b/mkversion.sh @@ -0,0 +1,18 @@ +#!/bin/sh +set -e + +if [ -z "$GOPACKAGE" ]; then + # not being run from 'go generate' + cd "$(dirname "$0")"/cmd +fi + +v="$( git describe --dirty --always | sed -e 's/-/+git/;y/-/./' )" + +cat < version_generated.go +// generated by mkversion.sh; do not edit +package cmd + +func init() { + Version = "$v" +} +EOF diff --git a/po/snappy.pot b/po/snappy.pot index 1933f5d468a..dd8c3f957f2 100644 --- a/po/snappy.pot +++ b/po/snappy.pot @@ -7,7 +7,7 @@ msgid "" msgstr "Project-Id-Version: snappy\n" "Report-Msgid-Bugs-To: snappy-devel@lists.ubuntu.com\n" - "POT-Creation-Date: 2016-05-19 09:54+0100\n" + "POT-Creation-Date: 2016-05-23 14:43+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -353,6 +353,9 @@ msgstr "" msgid "no interfaces found" msgstr "" +msgid "unavailable" +msgstr "" + #, c-format msgid "unsupported shell %v" msgstr "" diff --git a/run-checks b/run-checks index 803c6756607..6815e2f3be3 100755 --- a/run-checks +++ b/run-checks @@ -108,16 +108,9 @@ if [ ! -z "$STATIC" ]; then # cmd/* test sources are tagged so they are not included in # special integration test builds echo Checking 'cmd/*/*_test.go' build tagging - bad="" - for cmd in cmd/* ; do - nottagged=$(ls ${cmd}/*_test.go|grep -v integration_coverage_test|grep -v -F "$(grep -l '!integrationcoverage' ${cmd}/*_test.go)" || true) - - if [ -n "${nottagged}" ] ; then - echo these ${cmd} test files miss the '!integrationcoverage' tag: ${nottagged} - bad=1 - fi - done - if [ -n "${bad}" ] ; then + nottagged=$(grep -r --include '*_test.go' --exclude integration_coverage_test.go -L '!integrationcoverage' cmd/*/) + if [ -n "${nottagged}" ] ; then + echo these test files miss the '!integrationcoverage' tag: ${nottagged} exit 1 fi