From fd3821685802fd298e2e4b2ba06de5d7a3201dd0 Mon Sep 17 00:00:00 2001 From: James Otting Date: Mon, 2 Oct 2023 13:26:17 -0400 Subject: [PATCH] Add support for various 32-bit architectures (#7) --- .canon.yaml | 7 ++- .golangci.yaml | 1 + Makefile | 3 +- README.md | 19 ++++++--- config.go | 113 ++++++++++++++++++++++++++++++++++++++++--------- go.mod | 8 ++-- go.sum | 16 +++---- update.go | 24 ++++++++--- 8 files changed, 143 insertions(+), 48 deletions(-) diff --git a/.canon.yaml b/.canon.yaml index 3086b32..c20b464 100644 --- a/.canon.yaml +++ b/.canon.yaml @@ -1,3 +1,6 @@ canon-default: - image_amd64: "ghcr.io/viamrobotics/canon:amd64" - image_arm64: "ghcr.io/viamrobotics/canon:arm64" + image_amd64: "amd64/debian" + image_arm64: "arm64v8/debian" + image_386: "i386/debian" + image_arm: "arm32v7/debian" + image_arm_v6: "arm32v5/debian" diff --git a/.golangci.yaml b/.golangci.yaml index 7f3c66c..d4611ad 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -12,6 +12,7 @@ linters: - contextcheck - cyclop - deadcode + - depguard - exhaustivestruct - exhaustruct - forbidigo diff --git a/Makefile b/Makefile index 92bc034..44b16a9 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,10 @@ bin/canon: *.go go.mod go.sum canon_setup.sh go build -tags osusergo,netgo -ldflags "-s -w" -o bin/canon . bin/golangci-lint: - GOBIN=`pwd`/bin go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + GOBIN=`pwd`/bin go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2 lint: bin/golangci-lint + go mod tidy bin/golangci-lint run -v --fix clean: diff --git a/README.md b/README.md index 53a0656..f7912af 100644 --- a/README.md +++ b/README.md @@ -76,13 +76,16 @@ change based on the current project/directory, as well as with different argumen Profiles are defined with the following fields: -* `arch` The architecture (only amd64 or arm64 supported currently) to run the image as. +* `arch` The architecture (`amd64`, `arm64`, `386`, `arm`, or `arm/v6`) to run the image as. - Note the architecture does NOT have to match the host in most cases where emulation is set up. See [Emulation](#emulation) below - Defaults to the detected current architecture. * `image` The docker image used by this profile. Can be overriden by `-image` - - Note, this should NOT be defined if using the architecture-specific image options below. It will override them both. -* `image_amd64` The AMD64 specific image to use when that architecture is selected. -* `image_arm64` The ARM64 specific image to use when that architecture is selected. + - Note, this should NOT be defined if using the architecture-specific image options below. It will override them all. +* `image_amd64` The AMD64 (x86_64) specific image to use when that architecture is selected. +* `image_arm64` The ARM64 (aarch64) specific image to use when that architecture is selected. +* `image_386` The 386 (x86) specific image to use when that architecture is selected. +* `image_arm` The arm (armv7l/armhf) specific image to use when that architecture is selected. +* `image_arm_v6` The arm/v6 (armv6l) specific image to use when that architecture is selected. * `minimum_date` If the created timestamp of the image is older then this, force an update of the image. - This allows project maintainers to automatically notified canon (and canon users) when an update is needed for a project. - Obtain with `docker inspect -f '{{ .Created }}' IMAGE_NAME` @@ -114,10 +117,14 @@ opened in the same environment, and can make build/download caching inside the c can be set with the "persistent" value set to true. In this mode, any canon executions that use that profile will be run in the same container. Exiting a shell (or a command ending) will not terminate the container either. +### Listing active containers + +Run: `canon list` to list all currently running canon containers. + ### Terminating persistent containers -Run: `canon terminate` to terminate the container that would currently be used. -Optionally `-a` can be appended to terminate ALL canon-managed containers. +Run: `canon terminate` to terminate the container that would currently be used (what is shown from `canon config`.) +Optionally `-a` can be appended to terminate ALL canon-managed containers (everything shown by `canon list` above.) ## Emulation diff --git a/config.go b/config.go index 5301543..979608f 100644 --- a/config.go +++ b/config.go @@ -22,19 +22,22 @@ import ( type Profile struct { name string - Default bool `yaml:"default" mapstructure:"default"` - Image string `yaml:"image" mapstructure:"image"` - ImageAMD64 string `yaml:"image_amd64" mapstructure:"image_amd64"` - ImageARM64 string `yaml:"image_arm64" mapstructure:"image_arm64"` - Arch string `yaml:"arch" mapstructure:"arch"` - MinimumDate time.Time `yaml:"minimum_date" mapstructure:"minimum_date"` - UpdateInterval time.Duration `yaml:"update_interval" mapstructure:"update_interval"` - Persistent bool `yaml:"persistent" mapstructure:"persistent"` - SSH bool `yaml:"ssh" mapstructure:"ssh"` - NetRC bool `yaml:"netrc" mapstructure:"netrc"` - User string `yaml:"user" mapstructure:"user"` - Group string `yaml:"group" mapstructure:"group"` - Path string `yaml:"path" mapstructure:"path"` + Default bool `mapstructure:"default" yaml:"default"` + Image string `mapstructure:"image" yaml:"image"` + ImageAMD64 string `mapstructure:"image_amd64" yaml:"image_amd64"` + Image386 string `mapstructure:"image_386" yaml:"image_386"` + ImageARM64 string `mapstructure:"image_arm64" yaml:"image_arm64"` + ImageARM string `mapstructure:"image_arm" yaml:"image_arm"` + ImageARMv6 string `mapstructure:"image_arm_v6" yaml:"image_arm_v6"` + Arch string `mapstructure:"arch" yaml:"arch"` + MinimumDate time.Time `mapstructure:"minimum_date" yaml:"minimum_date"` + UpdateInterval time.Duration `mapstructure:"update_interval" yaml:"update_interval"` + Persistent bool `mapstructure:"persistent" yaml:"persistent"` + SSH bool `mapstructure:"ssh" yaml:"ssh"` + NetRC bool `mapstructure:"netrc" yaml:"netrc"` + User string `mapstructure:"user" yaml:"user"` + Group string `mapstructure:"group" yaml:"group"` + Path string `mapstructure:"path" yaml:"path"` } var activeProfile = &Profile{} @@ -142,16 +145,17 @@ func parseConfigs() error { flag.StringVar(&cfgPath, "config", userCfgPath, "config file") flag.StringVar(&profileName, "profile", defProfileName, "profile name") flag.StringVar(&activeProfile.Image, "image", activeProfile.Image, "docker image name") - flag.StringVar(&activeProfile.Arch, "arch", activeProfile.Arch, "architecture (\"amd64\" or \"arm64\")") + flag.StringVar(&activeProfile.Arch, "arch", activeProfile.Arch, "architecture (\"amd64\", \"arm64\", \"386\", \"arm\", \"arm/v6\")") flag.StringVar(&activeProfile.User, "user", activeProfile.User, "user to map to inside the canon environment") flag.StringVar(&activeProfile.Group, "group", activeProfile.Group, "group to map to inside the canon environment") flag.BoolVar(&activeProfile.SSH, "ssh", activeProfile.SSH, "mount ~/.ssh (read-only) and forward SSH_AUTH_SOCK to the canon environment") flag.BoolVar(&activeProfile.NetRC, "netrc", activeProfile.NetRC, "mount ~/.netrc (read-only) in the canon environment") flag.Parse() + // swap again in case a CLI arg would change arch swapArchImage(activeProfile) - return nil + return validateArch(activeProfile.Arch) } func findProjectConfig() (string, error) { @@ -247,8 +251,11 @@ func mergeProfile(in interface{}, out *Profile) error { if err := mapDecode(in, tempProf); err != nil { return err } - if tempProf.ImageAMD64 != "" || tempProf.ImageARM64 != "" { - out.Image = "" + for _, img := range []string{tempProf.ImageAMD64, tempProf.ImageARM64, tempProf.ImageARM, tempProf.ImageARMv6, tempProf.Image386} { + if img != "" { + out.Image = "" + break + } } return mapDecode(in, out) } @@ -380,15 +387,30 @@ func checkAll(args []string) bool { func swapArchImage(profile *Profile) { // abort if image is overridden and not one of the swapable options - if profile.Image != "" && profile.Image != profile.ImageAMD64 && profile.Image != profile.ImageARM64 { + var canSwap bool + for _, img := range []string{profile.ImageAMD64, profile.ImageARM64, profile.ImageARM, profile.ImageARMv6, profile.Image386} { + if profile.Image == "" || img == profile.Image { + canSwap = true + break + } + } + if !canSwap { return } - if profile.Arch == "amd64" && profile.ImageAMD64 != "" { + switch profile.Arch { + case "amd64": profile.Image = profile.ImageAMD64 - } - if profile.Arch == "arm64" && profile.ImageARM64 != "" { + case "arm64": profile.Image = profile.ImageARM64 + case "arm": + profile.Image = profile.ImageARM + case "arm/v6": + profile.Image = profile.ImageARMv6 + case "386": + profile.Image = profile.Image386 + default: + profile.Image = "" } } @@ -412,3 +434,52 @@ func mapDecode(iface interface{}, p *Profile) error { } return dec.Decode(iface) } + +func validateArch(arch string) error { + switch arch { + case "amd64": + fallthrough + case "arm64": + fallthrough + case "arm": + fallthrough + case "arm/v6": + fallthrough + case "386": + return nil + + case "armv7": + fallthrough + case "armv7l": + fallthrough + case "armhf": + fallthrough + case "arm/v7": + return errors.New("Invalid architecture: " + arch + "; Use just \"arm\"") + + case "armv6": + fallthrough + case "armv6l": + fallthrough + case "armel": + return errors.New("Invalid architecture: " + arch + "; Use \"arm/v6\"") + + case "x86_64": + return errors.New("Invalid architecture: " + arch + "; Use \"amd64\"") + + case "arm/v8": + fallthrough + case "aarch64": + return errors.New("Invalid architecture: " + arch + "; Use \"arm64\"") + + case "x86": + fallthrough + case "i386": + fallthrough + case "i686": + return errors.New("Invalid architecture: " + arch + "; Use \"386\"") + + default: + return errors.New("Invalid architecture: " + arch) + } +} diff --git a/go.mod b/go.mod index dc352f8..a22d00a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/viamrobotics/canon go 1.19 require ( - github.com/docker/docker v24.0.5+incompatible + github.com/docker/docker v24.0.6+incompatible github.com/mitchellh/mapstructure v1.5.0 github.com/moby/term v0.5.0 github.com/opencontainers/image-spec v1.0.2 @@ -25,10 +25,10 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/stretchr/testify v1.8.1 // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.12.0 // indirect - golang.org/x/sys v0.10.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/sys v0.12.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.11.0 // indirect + golang.org/x/tools v0.13.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect gotest.tools/v3 v3.4.0 // indirect ) diff --git a/go.sum b/go.sum index a872f65..b3fe2b4 100644 --- a/go.sum +++ b/go.sum @@ -9,8 +9,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY= -github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE= +github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -67,8 +67,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -78,8 +78,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= @@ -89,8 +89,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/update.go b/update.go index cc1936a..02f54a0 100644 --- a/update.go +++ b/update.go @@ -160,6 +160,9 @@ func checkUpdate(curProfile *Profile, all, force bool) error { // we want defaults but NOT the defaults for images prof.ImageAMD64 = "" prof.ImageARM64 = "" + prof.ImageARM = "" + prof.ImageARMv6 = "" + prof.Image386 = "" prof.Image = "" err = mapDecode(iface, prof) @@ -185,15 +188,24 @@ func checkUpdate(curProfile *Profile, all, force bool) error { func checkImageDate(profile *Profile, checkData ImageCheckData, force bool) []ImageDef { var imageCandidates, images []ImageDef - // Dual arch profile - switch { - case profile.ImageAMD64 != "" && profile.ImageARM64 != "": + // multi arch profiles + if profile.ImageAMD64 != "" { imageCandidates = append(imageCandidates, ImageDef{Image: profile.ImageAMD64, Platform: "linux/amd64"}) + } + if profile.ImageARM64 != "" { imageCandidates = append(imageCandidates, ImageDef{Image: profile.ImageARM64, Platform: "linux/arm64"}) - case profile.Image != "": + } + if profile.ImageARM != "" { + imageCandidates = append(imageCandidates, ImageDef{Image: profile.ImageARM, Platform: "linux/arm"}) + } + if profile.ImageARMv6 != "" { + imageCandidates = append(imageCandidates, ImageDef{Image: profile.ImageARMv6, Platform: "linux/arm/v6"}) + } + if profile.Image386 != "" { + imageCandidates = append(imageCandidates, ImageDef{Image: profile.Image386, Platform: "linux/386"}) + } + if profile.Image != "" { imageCandidates = append(imageCandidates, ImageDef{Image: profile.Image, Platform: "linux/" + profile.Arch}) - default: - return images } for _, i := range imageCandidates {