[lxc-devel] [lxd/master] Add oeprating system, architecture and possibly distribution name and version to the User-Agent

albertodonato on Github lxc-bot at linuxcontainers.org
Thu Sep 21 09:11:17 UTC 2017


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 314 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20170921/4511c1c3/attachment.bin>
-------------- next part --------------
From fc6528884afba76fb96043f376d58f4f0271e991 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 20 Sep 2017 15:18:06 +0200
Subject: [PATCH 1/6] shared/util: extract helper to get uname

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 lxd/api_1.0.go                       | 42 ++++---------------------------
 shared/osarch/architectures_linux.go | 17 ++++---------
 shared/util_linux.go                 | 48 ++++++++++++++++++++++++++++++++++++
 3 files changed, 58 insertions(+), 49 deletions(-)

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 7b68acdb3..0dd41bfaa 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -6,7 +6,6 @@ import (
 	"net/http"
 	"os"
 	"reflect"
-	"syscall"
 
 	"gopkg.in/lxc/go-lxc.v2"
 
@@ -140,42 +139,11 @@ func api10Get(d *Daemon, r *http.Request) Response {
 
 	srv.Auth = "trusted"
 
-	/*
-	 * Based on: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8
-	 * there is really no better way to do this, which is
-	 * unfortunate. Also, we ditch the more accepted CharsToString
-	 * version in that thread, since it doesn't seem as portable,
-	 * viz. github issue #206.
-	 */
-	uname := syscall.Utsname{}
-	if err := syscall.Uname(&uname); err != nil {
+	uname, err := shared.Uname()
+	if err != nil {
 		return InternalError(err)
 	}
 
-	kernel := ""
-	for _, c := range uname.Sysname {
-		if c == 0 {
-			break
-		}
-		kernel += string(byte(c))
-	}
-
-	kernelVersion := ""
-	for _, c := range uname.Release {
-		if c == 0 {
-			break
-		}
-		kernelVersion += string(byte(c))
-	}
-
-	kernelArchitecture := ""
-	for _, c := range uname.Machine {
-		if c == 0 {
-			break
-		}
-		kernelArchitecture += string(byte(c))
-	}
-
 	addresses, err := util.ListenAddresses(daemonConfig["core.https_address"].Get())
 	if err != nil {
 		return InternalError(err)
@@ -208,9 +176,9 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		CertificateFingerprint: certificateFingerprint,
 		Driver:                 "lxc",
 		DriverVersion:          lxc.Version(),
-		Kernel:                 kernel,
-		KernelArchitecture:     kernelArchitecture,
-		KernelVersion:          kernelVersion,
+		Kernel:                 uname.Sysname,
+		KernelArchitecture:     uname.Machine,
+		KernelVersion:          uname.Release,
 		Server:                 "lxd",
 		ServerPid:              os.Getpid(),
 		ServerVersion:          version.Version}
diff --git a/shared/osarch/architectures_linux.go b/shared/osarch/architectures_linux.go
index c95b58a73..a87e795a8 100644
--- a/shared/osarch/architectures_linux.go
+++ b/shared/osarch/architectures_linux.go
@@ -3,22 +3,15 @@
 package osarch
 
 import (
-	"syscall"
+	"github.com/lxc/lxd/shared"
 )
 
+// ArchitectureGetLocal returns the local hardware architecture
 func ArchitectureGetLocal() (string, error) {
-	uname := syscall.Utsname{}
-	if err := syscall.Uname(&uname); err != nil {
+	uname, err := shared.Uname()
+	if err != nil {
 		return ArchitectureDefault, err
 	}
 
-	architectureName := ""
-	for _, c := range uname.Machine {
-		if c == 0 {
-			break
-		}
-		architectureName += string(byte(c))
-	}
-
-	return architectureName, nil
+	return uname.Machine, nil
 }
diff --git a/shared/util_linux.go b/shared/util_linux.go
index 77daf1051..369898eb1 100644
--- a/shared/util_linux.go
+++ b/shared/util_linux.go
@@ -769,3 +769,51 @@ func GetErrno(err error) (errno error, iserrno bool) {
 
 	return nil, false
 }
+
+// Utsname returns the same info as syscall.Utsname, as strings
+type Utsname struct {
+	Sysname    string
+	Nodename   string
+	Release    string
+	Version    string
+	Machine    string
+	Domainname string
+}
+
+// Uname returns Utsname as strings
+func Uname() (*Utsname, error) {
+	/*
+	 * Based on: https://groups.google.com/forum/#!topic/golang-nuts/Jel8Bb-YwX8
+	 * there is really no better way to do this, which is
+	 * unfortunate. Also, we ditch the more accepted CharsToString
+	 * version in that thread, since it doesn't seem as portable,
+	 * viz. github issue #206.
+	 */
+
+	uname := syscall.Utsname{}
+	err := syscall.Uname(&uname)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Utsname{
+		Sysname:    intArrayToString(uname.Sysname),
+		Nodename:   intArrayToString(uname.Nodename),
+		Release:    intArrayToString(uname.Release),
+		Version:    intArrayToString(uname.Version),
+		Machine:    intArrayToString(uname.Machine),
+		Domainname: intArrayToString(uname.Domainname),
+	}, nil
+}
+
+func intArrayToString(arr [65]int8) string {
+	s := ""
+	for _, c := range arr {
+		if c == 0 {
+			break
+		}
+		s += string(byte(c))
+	}
+
+	return s
+}

From 0002355d93d88880ad708cb7f0cc84a82f573fe8 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 21 Sep 2017 10:48:22 +0200
Subject: [PATCH 2/6] shared/util: add helper to create tempfiles

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 shared/testhelpers/tempfile.go | 19 +++++++++++++++++++
 shared/util.go                 | 12 ++++++++++++
 2 files changed, 31 insertions(+)
 create mode 100644 shared/testhelpers/tempfile.go

diff --git a/shared/testhelpers/tempfile.go b/shared/testhelpers/tempfile.go
new file mode 100644
index 000000000..b1abebe7e
--- /dev/null
+++ b/shared/testhelpers/tempfile.go
@@ -0,0 +1,19 @@
+package testhelpers
+
+import (
+	"os"
+
+	"github.com/stretchr/testify/suite"
+
+	"github.com/lxc/lxd/shared"
+)
+
+// WriteTempFile writes content to a temporary file.
+func WriteTempFile(s suite.Suite, dir string, prefix string, content string) (string, func()) {
+	filename, err := shared.WriteTempFile(dir, prefix, content)
+	if err != nil {
+		s.T().Fatalf("failed to create temporary file: %v", err)
+	}
+
+	return filename, func() { os.Remove(filename) }
+}
diff --git a/shared/util.go b/shared/util.go
index e49e8d058..260b32c8a 100644
--- a/shared/util.go
+++ b/shared/util.go
@@ -857,3 +857,15 @@ func Round(x float64) int64 {
 
 	return int64(math.Floor(x + 0.5))
 }
+
+// WriteTempFile creates a temp file with the specified content
+func WriteTempFile(dir string, prefix string, content string) (string, error) {
+	f, err := ioutil.TempFile(dir, prefix)
+	if err != nil {
+		return "", err
+	}
+	defer f.Close()
+
+	_, err = f.WriteString(content)
+	return f.Name(), err
+}

From bb5ecf8fb53228ad93878e3e44e89aa95a2159f1 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Wed, 20 Sep 2017 20:35:12 +0200
Subject: [PATCH 3/6] shared/osarch: add missing architecture aliases

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 shared/osarch/architectures.go | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/shared/osarch/architectures.go b/shared/osarch/architectures.go
index 265d7cb02..e35b12772 100644
--- a/shared/osarch/architectures.go
+++ b/shared/osarch/architectures.go
@@ -28,12 +28,12 @@ var architectureNames = map[int]string{
 }
 
 var architectureAliases = map[int][]string{
-	ARCH_32BIT_INTEL_X86:             {"i386"},
+	ARCH_32BIT_INTEL_X86:             {"i386", "386"},
 	ARCH_64BIT_INTEL_X86:             {"amd64"},
-	ARCH_32BIT_ARMV7_LITTLE_ENDIAN:   {"armel", "armhf"},
+	ARCH_32BIT_ARMV7_LITTLE_ENDIAN:   {"armel", "armhf", "arm"},
 	ARCH_64BIT_ARMV8_LITTLE_ENDIAN:   {"arm64"},
 	ARCH_32BIT_POWERPC_BIG_ENDIAN:    {"powerpc"},
-	ARCH_64BIT_POWERPC_BIG_ENDIAN:    {"powerpc64"},
+	ARCH_64BIT_POWERPC_BIG_ENDIAN:    {"powerpc64", "ppc64"},
 	ARCH_64BIT_POWERPC_LITTLE_ENDIAN: {"ppc64el"},
 }
 

From dbdb5db247f112aa2863c7314d82d4b4f4b01bc6 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 21 Sep 2017 09:45:25 +0200
Subject: [PATCH 4/6] shared/osarch: add function for parsing /etc/os-release

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 shared/osarch/release.go      | 42 ++++++++++++++++++++
 shared/osarch/release_test.go | 90 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 132 insertions(+)
 create mode 100644 shared/osarch/release.go
 create mode 100644 shared/osarch/release_test.go

diff --git a/shared/osarch/release.go b/shared/osarch/release.go
new file mode 100644
index 000000000..22ba8d09b
--- /dev/null
+++ b/shared/osarch/release.go
@@ -0,0 +1,42 @@
+package osarch
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+)
+
+// GetLSBRelease returns a map with Linux distribution information
+func GetLSBRelease() (map[string]string, error) {
+	osRelease, err := getLSBRelease("/etc/os-release")
+	if os.IsNotExist(err) {
+		return getLSBRelease("/usr/lib/os-release")
+	}
+	return osRelease, err
+}
+
+func getLSBRelease(filename string) (map[string]string, error) {
+	osRelease := make(map[string]string)
+
+	data, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return osRelease, err
+	}
+	for i, line := range strings.Split(string(data), "\n") {
+		if len(line) == 0 {
+			continue
+		}
+		if strings.HasPrefix(line, "#") {
+			continue
+		}
+
+		tokens := strings.SplitN(line, "=", 2)
+		if len(tokens) != 2 {
+			return osRelease, fmt.Errorf("%s: invalid format on line %d", filename, i+1)
+		}
+		osRelease[tokens[0]] = strings.Trim(tokens[1], `'"`)
+	}
+
+	return osRelease, nil
+}
diff --git a/shared/osarch/release_test.go b/shared/osarch/release_test.go
new file mode 100644
index 000000000..55b830849
--- /dev/null
+++ b/shared/osarch/release_test.go
@@ -0,0 +1,90 @@
+package osarch
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/suite"
+
+	"github.com/lxc/lxd/shared/testhelpers"
+)
+
+type releaseTestSuite struct {
+	suite.Suite
+}
+
+func TestReleaseTestSuite(t *testing.T) {
+	suite.Run(t, new(releaseTestSuite))
+}
+
+func (s *releaseTestSuite) TestGetLSBRelease() {
+	content := `NAME="Ubuntu"
+ID="ubuntu"
+VERSION_ID="16.04"
+`
+	filename, cleanup := testhelpers.WriteTempFile(s.Suite, "", "os-release", content)
+	defer cleanup()
+
+	lsbRelease, err := getLSBRelease(filename)
+	s.Nil(err)
+	s.Equal(
+		map[string]string{
+			"NAME":       "Ubuntu",
+			"ID":         "ubuntu",
+			"VERSION_ID": "16.04",
+		}, lsbRelease)
+}
+
+func (s *releaseTestSuite) TestGetLSBReleaseSingleQuotes() {
+	content := `NAME='Ubuntu'`
+	filename, cleanup := testhelpers.WriteTempFile(s.Suite, "", "os-release", content)
+	defer cleanup()
+
+	lsbRelease, err := getLSBRelease(filename)
+	s.Nil(err)
+	s.Equal(map[string]string{"NAME": "Ubuntu"}, lsbRelease)
+}
+
+func (s *releaseTestSuite) TestGetLSBReleaseNoQuotes() {
+	content := `NAME=Ubuntu`
+	filename, cleanup := testhelpers.WriteTempFile(s.Suite, "", "os-release", content)
+	defer cleanup()
+
+	lsbRelease, err := getLSBRelease(filename)
+	s.Nil(err)
+	s.Equal(map[string]string{"NAME": "Ubuntu"}, lsbRelease)
+}
+
+func (s *releaseTestSuite) TestGetLSBReleaseSkipCommentsEmpty() {
+	content := `
+NAME="Ubuntu"
+
+ID="ubuntu"
+# skip this line
+VERSION_ID="16.04"
+`
+	filename, cleanup := testhelpers.WriteTempFile(s.Suite, "", "os-release", content)
+	defer cleanup()
+
+	lsbRelease, err := getLSBRelease(filename)
+	s.Nil(err)
+	s.Equal(
+		map[string]string{
+			"NAME":       "Ubuntu",
+			"ID":         "ubuntu",
+			"VERSION_ID": "16.04",
+		}, lsbRelease)
+}
+
+func (s *releaseTestSuite) TestGetLSBReleaseInvalidLine() {
+	content := `
+NAME="Ubuntu"
+this is invalid
+ID="ubuntu"
+`
+	filename, cleanup := testhelpers.WriteTempFile(s.Suite, "", "os-release", content)
+	defer cleanup()
+
+	_, err := getLSBRelease(filename)
+	s.EqualError(err, fmt.Sprintf("%s: invalid format on line 3", filename))
+}

From 6f23fc799bef0e468a00b8d993adf51b80c83ddd Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 21 Sep 2017 09:50:40 +0200
Subject: [PATCH 5/6] shared/version: add helper to get platform-specific
 versions

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 shared/version/platform_linux.go  | 32 ++++++++++++++++++++++++++++++++
 shared/version/platform_others.go |  7 +++++++
 2 files changed, 39 insertions(+)
 create mode 100644 shared/version/platform_linux.go
 create mode 100644 shared/version/platform_others.go

diff --git a/shared/version/platform_linux.go b/shared/version/platform_linux.go
new file mode 100644
index 000000000..252a80bc3
--- /dev/null
+++ b/shared/version/platform_linux.go
@@ -0,0 +1,32 @@
+// +build linux
+
+package version
+
+import (
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/osarch"
+)
+
+func getPlatformVersionStrings() []string {
+	versions := []string{}
+
+	// add kernel version
+	uname, err := shared.Uname()
+	if err == nil {
+		versions = append(versions, uname.Release)
+	}
+
+	// add distribution info
+	lsbRelease, err := osarch.GetLSBRelease()
+	if err == nil {
+		distroName, ok := lsbRelease["NAME"]
+		if ok {
+			versions = append(versions, distroName)
+		}
+		distroRelease, ok := lsbRelease["VERSION_ID"]
+		if ok {
+			versions = append(versions, distroRelease)
+		}
+	}
+	return versions
+}
diff --git a/shared/version/platform_others.go b/shared/version/platform_others.go
new file mode 100644
index 000000000..d29148682
--- /dev/null
+++ b/shared/version/platform_others.go
@@ -0,0 +1,7 @@
+// +build !linux
+
+package version
+
+func getPlatformVersionStrings() []string {
+	return []string{}
+}

From da95c8ced76dab98f08c69d15a9cb04f17674ee6 Mon Sep 17 00:00:00 2001
From: Alberto Donato <alberto.donato at canonical.com>
Date: Thu, 21 Sep 2017 09:56:12 +0200
Subject: [PATCH 6/6] shared/version: include OS, architecture and possibly
 kernel and distro info in User-Agent

Signed-off-by: Alberto Donato <alberto.donato at canonical.com>
---
 shared/version/flex.go | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/shared/version/flex.go b/shared/version/flex.go
index 5feeedbba..48a3537a4 100644
--- a/shared/version/flex.go
+++ b/shared/version/flex.go
@@ -1,10 +1,33 @@
 package version
 
+import (
+	"fmt"
+	"runtime"
+	"strings"
+
+	"github.com/lxc/lxd/shared/osarch"
+)
+
 // Version contains the LXD version number
 var Version = "2.18"
 
 // UserAgent contains a string suitable as a user-agent
-var UserAgent = "LXD " + Version
+var UserAgent = getUserAgent()
 
 // APIVersion contains the API base version. Only bumped for backward incompatible changes.
 var APIVersion = "1.0"
+
+func getUserAgent() string {
+	archID, err := osarch.ArchitectureId(runtime.GOARCH)
+	if err != nil {
+		panic(err)
+	}
+	arch, err := osarch.ArchitectureName(archID)
+	if err != nil {
+		panic(err)
+	}
+
+	tokens := []string{strings.Title(runtime.GOOS), arch}
+	tokens = append(tokens, getPlatformVersionStrings()...)
+	return fmt.Sprintf("LXD %s (%s)", Version, strings.Join(tokens, "; "))
+}


More information about the lxc-devel mailing list