Unverified Commit fe733793 authored by Darren Shepherd's avatar Darren Shepherd Committed by GitHub

Merge pull request #1770 from ibuildthecloud/etcd

Add embedded etcd support
parents e4a76d56 f4ff2bf3
...@@ -27,14 +27,6 @@ RUN if [ "${ARCH}" = 'amd64' ]; then \ ...@@ -27,14 +27,6 @@ RUN if [ "${ARCH}" = 'amd64' ]; then \
ARG SELINUX=true ARG SELINUX=true
ENV SELINUX $SELINUX ENV SELINUX $SELINUX
ARG DQLITE=true
ENV DQLITE $DQLITE
COPY --from=rancher/dqlite-build:v1.4.1-r1 /dist/artifacts /usr/src/
RUN if [ "$DQLITE" = true ]; then \
tar xzf /usr/src/dqlite.tgz -C / && \
apk add --allow-untrusted /usr/local/packages/*.apk \
;fi
ENV GO111MODULE off ENV GO111MODULE off
ENV DAPPER_RUN_ARGS --privileged -v k3s-cache:/go/src/github.com/rancher/k3s/.cache ENV DAPPER_RUN_ARGS --privileged -v k3s-cache:/go/src/github.com/rancher/k3s/.cache
ENV DAPPER_ENV REPO TAG DRONE_TAG IMAGE_NAME SKIP_VALIDATE GCLOUD_AUTH ENV DAPPER_ENV REPO TAG DRONE_TAG IMAGE_NAME SKIP_VALIDATE GCLOUD_AUTH
......
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"github.com/rancher/k3s/pkg/data" "github.com/rancher/k3s/pkg/data"
"github.com/rancher/k3s/pkg/datadir" "github.com/rancher/k3s/pkg/datadir"
"github.com/rancher/k3s/pkg/untar" "github.com/rancher/k3s/pkg/untar"
"github.com/rancher/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
...@@ -24,8 +25,8 @@ func main() { ...@@ -24,8 +25,8 @@ func main() {
app := cmds.NewApp() app := cmds.NewApp()
app.Commands = []cli.Command{ app.Commands = []cli.Command{
cmds.NewServerCommand(wrap("k3s-server", os.Args)), cmds.NewServerCommand(wrap(version.Program+"-server", os.Args)),
cmds.NewAgentCommand(wrap("k3s-agent", os.Args)), cmds.NewAgentCommand(wrap(version.Program+"-agent", os.Args)),
cmds.NewKubectlCommand(externalCLIAction("kubectl")), cmds.NewKubectlCommand(externalCLIAction("kubectl")),
cmds.NewCRICTL(externalCLIAction("crictl")), cmds.NewCRICTL(externalCLIAction("crictl")),
cmds.NewCtrCommand(externalCLIAction("ctr")), cmds.NewCtrCommand(externalCLIAction("ctr")),
...@@ -88,7 +89,7 @@ func stageAndRun(dataDir string, cmd string, args []string) error { ...@@ -88,7 +89,7 @@ func stageAndRun(dataDir string, cmd string, args []string) error {
if err := os.Setenv("PATH", filepath.Join(dir, "bin")+":"+os.Getenv("PATH")+":"+filepath.Join(dir, "bin/aux")); err != nil { if err := os.Setenv("PATH", filepath.Join(dir, "bin")+":"+os.Getenv("PATH")+":"+filepath.Join(dir, "bin/aux")); err != nil {
return err return err
} }
if err := os.Setenv("K3S_DATA_DIR", dir); err != nil { if err := os.Setenv(version.ProgramUpper+"_DATA_DIR", dir); err != nil {
return err return err
} }
......
...@@ -66,7 +66,6 @@ require ( ...@@ -66,7 +66,6 @@ require (
github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e // indirect github.com/bhendo/go-powershell v0.0.0-20190719160123-219e7fb4e41e // indirect
github.com/bronze1man/goStrongswanVici v0.0.0-20190828090544-27d02f80ba40 // indirect github.com/bronze1man/goStrongswanVici v0.0.0-20190828090544-27d02f80ba40 // indirect
github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 // indirect
github.com/canonical/go-dqlite v1.5.1
github.com/containerd/cgroups v0.0.0-00010101000000-000000000000 // indirect github.com/containerd/cgroups v0.0.0-00010101000000-000000000000 // indirect
github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69 github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69
github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect github.com/containerd/continuity v0.0.0-20190827140505-75bee3e2ccb6 // indirect
...@@ -81,18 +80,17 @@ require ( ...@@ -81,18 +80,17 @@ require (
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f
github.com/docker/docker v1.4.2-0.20191205034852-d163fbba3c82 github.com/docker/docker v1.4.2-0.20191205034852-d163fbba3c82
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 // indirect
github.com/go-bindata/go-bindata v3.1.2+incompatible github.com/go-bindata/go-bindata v3.1.2+incompatible
github.com/go-sql-driver/mysql v1.4.1 github.com/go-sql-driver/mysql v1.4.1
github.com/gogo/googleapis v1.3.0 // indirect github.com/gogo/googleapis v1.3.0 // indirect
github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2 github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.3 github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.1 github.com/gorilla/websocket v1.4.1
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d // indirect github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d // indirect
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 // indirect
github.com/kubernetes-sigs/cri-tools v0.0.0-00010101000000-000000000000 github.com/kubernetes-sigs/cri-tools v0.0.0-00010101000000-000000000000
github.com/lib/pq v1.1.1 github.com/lib/pq v1.1.1
github.com/lxc/lxd v0.0.0-20191108214106-60ea15630455
github.com/mattn/go-sqlite3 v1.13.0 github.com/mattn/go-sqlite3 v1.13.0
github.com/natefinch/lumberjack v2.0.0+incompatible github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/opencontainers/runc v1.0.0-rc10 github.com/opencontainers/runc v1.0.0-rc10
...@@ -110,12 +108,13 @@ require ( ...@@ -110,12 +108,13 @@ require (
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
github.com/tchap/go-patricia v2.3.0+incompatible // indirect github.com/tchap/go-patricia v2.3.0+incompatible // indirect
github.com/urfave/cli v1.22.2 github.com/urfave/cli v1.22.2
// 54ba958 is v3.4.9
go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e
google.golang.org/grpc v1.26.0 google.golang.org/grpc v1.26.0
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 // indirect
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
k8s.io/api v0.18.0 k8s.io/api v0.18.0
k8s.io/apimachinery v0.18.0 k8s.io/apimachinery v0.18.0
...@@ -126,4 +125,5 @@ require ( ...@@ -126,4 +125,5 @@ require (
k8s.io/cri-api v0.0.0 k8s.io/cri-api v0.0.0
k8s.io/klog v1.0.0 k8s.io/klog v1.0.0
k8s.io/kubernetes v1.18.0 k8s.io/kubernetes v1.18.0
sigs.k8s.io/yaml v1.2.0
) )
...@@ -207,8 +207,6 @@ github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8 ...@@ -207,8 +207,6 @@ github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4 h1:GY1+t5Dr9OKADM64SYnQjw/w99HMYvQ0A8/JoUkxVmc=
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
...@@ -223,8 +221,6 @@ github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG ...@@ -223,8 +221,6 @@ github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG
github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE= github.com/go-bindata/go-bindata v3.1.2+incompatible h1:5vjJMVhowQdPzjE1LdxyFF7YFTXg5IgGVW4gBr5IbvE=
github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
...@@ -446,13 +442,10 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u ...@@ -446,13 +442,10 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/errors v0.0.0-20180806074554-22422dad46e1/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc= github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc=
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q= github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 h1:UUHMLvzt/31azWTN/ifGWef4WUqvXk0iRqdhdy/2uzI=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U= github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/juju/testing v0.0.0-20190613124551-e81189438503/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc= github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2 h1:Pp8RxiF4rSoXP9SED26WCfNB28/dwTDpPXS8XMJR8rc=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA= github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
...@@ -494,8 +487,6 @@ github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH ...@@ -494,8 +487,6 @@ github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH
github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk= github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk=
github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao= github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao=
github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58=
github.com/lxc/lxd v0.0.0-20191108214106-60ea15630455 h1:gQQV7It0kjZxMLJkS/+5Mc6w0zM6pKGzl3OS0h2RHrY=
github.com/lxc/lxd v0.0.0-20191108214106-60ea15630455/go.mod h1:2BaZflfwsv8a3uy3/Vw+de4Avn4DSrAiqaHJjCIXMV4=
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
...@@ -814,6 +805,8 @@ go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= ...@@ -814,6 +805,8 @@ go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f h1:pBCD+Z7cy5WPTq+R6MmJJvDRpn88cp7bmTypBsn91g4=
go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA= go.mongodb.org/mongo-driver v1.1.2 h1:jxcFYjlkl8xaERsgLo+RNquI0epW6zuy/ZRQs6jnrFA=
...@@ -948,7 +941,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm ...@@ -948,7 +941,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
...@@ -1027,8 +1019,6 @@ gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3M ...@@ -1027,8 +1019,6 @@ gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3M
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5 h1:E846t8CnR+lv5nE+VuiKTDG/v1U2stad0QzddfJC7kY=
gopkg.in/robfig/cron.v2 v2.0.0-20150107220207-be2e0b0deed5/go.mod h1:hiOFpYm0ZJbusNj2ywpbrXowU3G8U6GIQzqn2mw1UIE=
gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
......
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
"github.com/rancher/k3s/pkg/clientaccess" "github.com/rancher/k3s/pkg/clientaccess"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/daemons/control" "github.com/rancher/k3s/pkg/daemons/control"
"github.com/rancher/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/net"
...@@ -73,12 +74,12 @@ func getNodeNamedCrt(nodeName, nodePasswordFile string) HTTPRequester { ...@@ -73,12 +74,12 @@ func getNodeNamedCrt(nodeName, nodePasswordFile string) HTTPRequester {
req.SetBasicAuth(username, password) req.SetBasicAuth(username, password)
} }
req.Header.Set("K3s-Node-Name", nodeName) req.Header.Set(version.Program+"-Node-Name", nodeName)
nodePassword, err := ensureNodePassword(nodePasswordFile) nodePassword, err := ensureNodePassword(nodePasswordFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("K3s-Node-Password", nodePassword) req.Header.Set(version.Program+"-Node-Password", nodePassword)
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
...@@ -142,7 +143,7 @@ func upgradeOldNodePasswordPath(oldNodePasswordFile, newNodePasswordFile string) ...@@ -142,7 +143,7 @@ func upgradeOldNodePasswordPath(oldNodePasswordFile, newNodePasswordFile string)
} }
func getServingCert(nodeName, servingCertFile, servingKeyFile, nodePasswordFile string, info *clientaccess.Info) (*tls.Certificate, error) { func getServingCert(nodeName, servingCertFile, servingKeyFile, nodePasswordFile string, info *clientaccess.Info) (*tls.Certificate, error) {
servingCert, err := Request("/v1-k3s/serving-kubelet.crt", info, getNodeNamedCrt(nodeName, nodePasswordFile)) servingCert, err := Request("/v1-"+version.Program+"/serving-kubelet.crt", info, getNodeNamedCrt(nodeName, nodePasswordFile))
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -166,7 +167,7 @@ func getServingCert(nodeName, servingCertFile, servingKeyFile, nodePasswordFile ...@@ -166,7 +167,7 @@ func getServingCert(nodeName, servingCertFile, servingKeyFile, nodePasswordFile
func getHostFile(filename, keyFile string, info *clientaccess.Info) error { func getHostFile(filename, keyFile string, info *clientaccess.Info) error {
basename := filepath.Base(filename) basename := filepath.Base(filename)
fileBytes, err := clientaccess.Get("/v1-k3s/"+basename, info) fileBytes, err := clientaccess.Get("/v1-"+version.Program+"/"+basename, info)
if err != nil { if err != nil {
return err return err
} }
...@@ -206,7 +207,7 @@ func splitCertKeyPEM(bytes []byte) (certPem []byte, keyPem []byte) { ...@@ -206,7 +207,7 @@ func splitCertKeyPEM(bytes []byte) (certPem []byte, keyPem []byte) {
func getNodeNamedHostFile(filename, keyFile, nodeName, nodePasswordFile string, info *clientaccess.Info) error { func getNodeNamedHostFile(filename, keyFile, nodeName, nodePasswordFile string, info *clientaccess.Info) error {
basename := filepath.Base(filename) basename := filepath.Base(filename)
fileBytes, err := Request("/v1-k3s/"+basename, info, getNodeNamedCrt(nodeName, nodePasswordFile)) fileBytes, err := Request("/v1-"+version.Program+"/"+basename, info, getNodeNamedCrt(nodeName, nodePasswordFile))
if err != nil { if err != nil {
return err return err
} }
...@@ -282,7 +283,7 @@ func locateOrGenerateResolvConf(envInfo *cmds.Agent) string { ...@@ -282,7 +283,7 @@ func locateOrGenerateResolvConf(envInfo *cmds.Agent) string {
} }
} }
tmpConf := filepath.Join(os.TempDir(), "k3s-resolv.conf") tmpConf := filepath.Join(os.TempDir(), version.Program+"-resolv.conf")
if err := ioutil.WriteFile(tmpConf, []byte("nameserver 8.8.8.8\n"), 0444); err != nil { if err := ioutil.WriteFile(tmpConf, []byte("nameserver 8.8.8.8\n"), 0444); err != nil {
logrus.Error(err) logrus.Error(err)
return "" return ""
...@@ -385,13 +386,13 @@ func get(envInfo *cmds.Agent, proxy proxy.Proxy) (*config.Node, error) { ...@@ -385,13 +386,13 @@ func get(envInfo *cmds.Agent, proxy proxy.Proxy) (*config.Node, error) {
return nil, err return nil, err
} }
clientK3sControllerCert := filepath.Join(envInfo.DataDir, "client-k3s-controller.crt") clientK3sControllerCert := filepath.Join(envInfo.DataDir, "client-"+version.Program+"-controller.crt")
clientK3sControllerKey := filepath.Join(envInfo.DataDir, "client-k3s-controller.key") clientK3sControllerKey := filepath.Join(envInfo.DataDir, "client-"+version.Program+"-controller.key")
if err := getHostFile(clientK3sControllerCert, clientK3sControllerKey, info); err != nil { if err := getHostFile(clientK3sControllerCert, clientK3sControllerKey, info); err != nil {
return nil, err return nil, err
} }
kubeconfigK3sController := filepath.Join(envInfo.DataDir, "k3scontroller.kubeconfig") kubeconfigK3sController := filepath.Join(envInfo.DataDir, version.Program+"controller.kubeconfig")
if err := control.KubeConfig(kubeconfigK3sController, proxy.APIServerURL(), serverCAFile, clientK3sControllerCert, clientK3sControllerKey); err != nil { if err := control.KubeConfig(kubeconfigK3sController, proxy.APIServerURL(), serverCAFile, clientK3sControllerCert, clientK3sControllerKey); err != nil {
return nil, err return nil, err
} }
...@@ -488,7 +489,7 @@ func get(envInfo *cmds.Agent, proxy proxy.Proxy) (*config.Node, error) { ...@@ -488,7 +489,7 @@ func get(envInfo *cmds.Agent, proxy proxy.Proxy) (*config.Node, error) {
} }
func getConfig(info *clientaccess.Info) (*config.Control, error) { func getConfig(info *clientaccess.Info) (*config.Control, error) {
data, err := clientaccess.Get("/v1-k3s/config", info) data, err := clientaccess.Get("/v1-"+version.Program+"/config", info)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"github.com/rancher/k3s/pkg/agent/templates" "github.com/rancher/k3s/pkg/agent/templates"
util2 "github.com/rancher/k3s/pkg/agent/util" util2 "github.com/rancher/k3s/pkg/agent/util"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"google.golang.org/grpc" "google.golang.org/grpc"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
...@@ -233,7 +234,7 @@ func setupContainerdConfig(ctx context.Context, cfg *config.Node) error { ...@@ -233,7 +234,7 @@ func setupContainerdConfig(ctx context.Context, cfg *config.Node) error {
containerdConfig.SELinuxEnabled = selEnabled containerdConfig.SELinuxEnabled = selEnabled
} }
if containerdConfig.SELinuxEnabled && !selConfigured { if containerdConfig.SELinuxEnabled && !selConfigured {
logrus.Warnf("SELinux is enabled for k3s but process is not running in context '%s', k3s-selinux policy may need to be applied", SELinuxContextType) logrus.Warnf("SELinux is enabled for "+version.Program+" but process is not running in context '%s', "+version.Program+"-selinux policy may need to be applied", SELinuxContextType)
} }
containerdTemplateBytes, err := ioutil.ReadFile(cfg.Containerd.Template) containerdTemplateBytes, err := ioutil.ReadFile(cfg.Containerd.Template)
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/rancher/k3s/pkg/agent/util" "github.com/rancher/k3s/pkg/agent/util"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/client-go/kubernetes/typed/core/v1" v1 "k8s.io/client-go/kubernetes/typed/core/v1"
...@@ -142,7 +143,7 @@ func createFlannelConf(nodeConfig *config.Node) error { ...@@ -142,7 +143,7 @@ func createFlannelConf(nodeConfig *config.Node) error {
func setupStrongSwan(nodeConfig *config.Node) error { func setupStrongSwan(nodeConfig *config.Node) error {
// if data dir env is not set point to root // if data dir env is not set point to root
dataDir := os.Getenv("K3S_DATA_DIR") dataDir := os.Getenv(version.ProgramUpper + "_DATA_DIR")
if dataDir == "" { if dataDir == "" {
dataDir = "/" dataDir = "/"
} }
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"sync" "sync"
"github.com/google/tcpproxy" "github.com/google/tcpproxy"
"github.com/rancher/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
...@@ -27,9 +28,9 @@ type LoadBalancer struct { ...@@ -27,9 +28,9 @@ type LoadBalancer struct {
nextServerIndex int nextServerIndex int
} }
const ( var (
SupervisorServiceName = "k3s-agent-load-balancer" SupervisorServiceName = version.Program + "-agent-load-balancer"
APIServerServiceName = "k3s-api-server-agent-load-balancer" APIServerServiceName = version.Program + "-api-server-agent-load-balancer"
) )
func New(dataDir, serviceName, serverURL string) (_lb *LoadBalancer, _err error) { func New(dataDir, serviceName, serverURL string) (_lb *LoadBalancer, _err error) {
......
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
daemonconfig "github.com/rancher/k3s/pkg/daemons/config" daemonconfig "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/nodeconfig" "github.com/rancher/k3s/pkg/nodeconfig"
"github.com/rancher/k3s/pkg/rootless" "github.com/rancher/k3s/pkg/rootless"
"github.com/rancher/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
...@@ -32,10 +33,10 @@ import ( ...@@ -32,10 +33,10 @@ import (
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
) )
const ( var (
InternalIPLabel = "k3s.io/internal-ip" InternalIPLabel = version.Program + ".io/internal-ip"
ExternalIPLabel = "k3s.io/external-ip" ExternalIPLabel = version.Program + ".io/external-ip"
HostnameLabel = "k3s.io/hostname" HostnameLabel = version.Program + ".io/hostname"
) )
func run(ctx context.Context, cfg cmds.Agent, proxy proxy.Proxy) error { func run(ctx context.Context, cfg cmds.Agent, proxy proxy.Proxy) error {
......
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/rancher/k3s/pkg/agent/proxy" "github.com/rancher/k3s/pkg/agent/proxy"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/version"
"github.com/rancher/remotedialer" "github.com/rancher/remotedialer"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
...@@ -159,7 +160,7 @@ func Setup(ctx context.Context, config *config.Node, proxy proxy.Proxy) error { ...@@ -159,7 +160,7 @@ func Setup(ctx context.Context, config *config.Node, proxy proxy.Proxy) error {
} }
func connect(rootCtx context.Context, waitGroup *sync.WaitGroup, address string, tlsConfig *tls.Config) context.CancelFunc { func connect(rootCtx context.Context, waitGroup *sync.WaitGroup, address string, tlsConfig *tls.Config) context.CancelFunc {
wsURL := fmt.Sprintf("wss://%s/v1-k3s/connect", address) wsURL := fmt.Sprintf("wss://%s/v1-"+version.Program+"/connect", address)
ws := &websocket.Dialer{ ws := &websocket.Dialer{
TLSClientConfig: tlsConfig, TLSClientConfig: tlsConfig,
} }
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/rancher/k3s/pkg/datadir" "github.com/rancher/k3s/pkg/datadir"
"github.com/rancher/k3s/pkg/netutil" "github.com/rancher/k3s/pkg/netutil"
"github.com/rancher/k3s/pkg/token" "github.com/rancher/k3s/pkg/token"
"github.com/rancher/k3s/pkg/version"
"github.com/rancher/wrangler/pkg/signals" "github.com/rancher/wrangler/pkg/signals"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -48,7 +49,7 @@ func Run(ctx *cli.Context) error { ...@@ -48,7 +49,7 @@ func Run(ctx *cli.Context) error {
cmds.AgentConfig.NodeIP = netutil.GetIPFromInterface(cmds.AgentConfig.FlannelIface) cmds.AgentConfig.NodeIP = netutil.GetIPFromInterface(cmds.AgentConfig.FlannelIface)
} }
logrus.Infof("Starting k3s agent %s", ctx.App.Version) logrus.Infof("Starting "+version.Program+" agent %s", ctx.App.Version)
dataDir, err := datadir.LocalHome(cmds.AgentConfig.DataDir, cmds.AgentConfig.Rootless) dataDir, err := datadir.LocalHome(cmds.AgentConfig.DataDir, cmds.AgentConfig.Rootless)
if err != nil { if err != nil {
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/rancher/k3s/pkg/version"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
...@@ -57,7 +58,7 @@ var ( ...@@ -57,7 +58,7 @@ var (
NodeNameFlag = cli.StringFlag{ NodeNameFlag = cli.StringFlag{
Name: "node-name", Name: "node-name",
Usage: "(agent/node) Node name", Usage: "(agent/node) Node name",
EnvVar: "K3S_NODE_NAME", EnvVar: version.ProgramUpper + "_NODE_NAME",
Destination: &AgentConfig.NodeName, Destination: &AgentConfig.NodeName,
} }
WithNodeIDFlag = cli.BoolFlag{ WithNodeIDFlag = cli.BoolFlag{
...@@ -79,7 +80,7 @@ var ( ...@@ -79,7 +80,7 @@ var (
Name: "private-registry", Name: "private-registry",
Usage: "(agent/runtime) Private registry configuration file", Usage: "(agent/runtime) Private registry configuration file",
Destination: &AgentConfig.PrivateRegistry, Destination: &AgentConfig.PrivateRegistry,
Value: "/etc/rancher/k3s/registries.yaml", Value: "/etc/rancher/" + version.Program + "/registries.yaml",
} }
PauseImageFlag = cli.StringFlag{ PauseImageFlag = cli.StringFlag{
Name: "pause-image", Name: "pause-image",
...@@ -105,7 +106,7 @@ var ( ...@@ -105,7 +106,7 @@ var (
ResolvConfFlag = cli.StringFlag{ ResolvConfFlag = cli.StringFlag{
Name: "resolv-conf", Name: "resolv-conf",
Usage: "(agent/networking) Kubelet resolv.conf file", Usage: "(agent/networking) Kubelet resolv.conf file",
EnvVar: "K3S_RESOLV_CONF", EnvVar: version.ProgramUpper + "_RESOLV_CONF",
Destination: &AgentConfig.ResolvConf, Destination: &AgentConfig.ResolvConf,
} }
ExtraKubeletArgs = cli.StringSliceFlag{ ExtraKubeletArgs = cli.StringSliceFlag{
...@@ -150,26 +151,26 @@ func NewAgentCommand(action func(ctx *cli.Context) error) cli.Command { ...@@ -150,26 +151,26 @@ func NewAgentCommand(action func(ctx *cli.Context) error) cli.Command {
cli.StringFlag{ cli.StringFlag{
Name: "token,t", Name: "token,t",
Usage: "(cluster) Token to use for authentication", Usage: "(cluster) Token to use for authentication",
EnvVar: "K3S_TOKEN", EnvVar: version.ProgramUpper + "_TOKEN",
Destination: &AgentConfig.Token, Destination: &AgentConfig.Token,
}, },
cli.StringFlag{ cli.StringFlag{
Name: "token-file", Name: "token-file",
Usage: "(cluster) Token file to use for authentication", Usage: "(cluster) Token file to use for authentication",
EnvVar: "K3S_TOKEN_FILE", EnvVar: version.ProgramUpper + "_TOKEN_FILE",
Destination: &AgentConfig.TokenFile, Destination: &AgentConfig.TokenFile,
}, },
cli.StringFlag{ cli.StringFlag{
Name: "server,s", Name: "server,s",
Usage: "(cluster) Server to connect to", Usage: "(cluster) Server to connect to",
EnvVar: "K3S_URL", EnvVar: version.ProgramUpper + "_URL",
Destination: &AgentConfig.ServerURL, Destination: &AgentConfig.ServerURL,
}, },
cli.StringFlag{ cli.StringFlag{
Name: "data-dir,d", Name: "data-dir,d",
Usage: "(agent/data) Folder to hold state", Usage: "(agent/data) Folder to hold state",
Destination: &AgentConfig.DataDir, Destination: &AgentConfig.DataDir,
Value: "/var/lib/rancher/k3s", Value: "/var/lib/rancher/" + version.Program + "",
}, },
NodeNameFlag, NodeNameFlag,
WithNodeIDFlag, WithNodeIDFlag,
...@@ -200,7 +201,7 @@ func NewAgentCommand(action func(ctx *cli.Context) error) cli.Command { ...@@ -200,7 +201,7 @@ func NewAgentCommand(action func(ctx *cli.Context) error) cli.Command {
Name: "cluster-secret", Name: "cluster-secret",
Usage: "(deprecated) use --token", Usage: "(deprecated) use --token",
Destination: &AgentConfig.ClusterSecret, Destination: &AgentConfig.ClusterSecret,
EnvVar: "K3S_CLUSTER_SECRET", EnvVar: version.ProgramUpper + "_CLUSTER_SECRET",
}, },
}, },
} }
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
"github.com/natefinch/lumberjack" "github.com/natefinch/lumberjack"
"github.com/rancher/k3s/pkg/version"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
...@@ -90,7 +91,7 @@ func runWithLogging() error { ...@@ -90,7 +91,7 @@ func runWithLogging() error {
l = io.MultiWriter(l, os.Stderr) l = io.MultiWriter(l, os.Stderr)
} }
args := append([]string{"k3s"}, os.Args[1:]...) args := append([]string{version.Program}, os.Args[1:]...)
cmd := reexec.Command(args...) cmd := reexec.Command(args...)
cmd.Env = os.Environ() cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "_K3S_LOG_REEXEC_=true") cmd.Env = append(cmd.Env, "_K3S_LOG_REEXEC_=true")
......
...@@ -33,7 +33,7 @@ func NewApp() *cli.App { ...@@ -33,7 +33,7 @@ func NewApp() *cli.App {
Name: "debug", Name: "debug",
Usage: "Turn on debug logs", Usage: "Turn on debug logs",
Destination: &Debug, Destination: &Debug,
EnvVar: "K3S_DEBUG", EnvVar: version.ProgramUpper + "_DEBUG",
}, },
} }
......
package cmds package cmds
import ( import (
"github.com/rancher/k3s/pkg/version"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
...@@ -69,7 +70,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -69,7 +70,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
AlsoLogToStderr, AlsoLogToStderr,
cli.StringFlag{ cli.StringFlag{
Name: "bind-address", Name: "bind-address",
Usage: "(listener) k3s bind address (default: 0.0.0.0)", Usage: "(listener) " + version.Program + " bind address (default: 0.0.0.0)",
Destination: &ServerConfig.BindAddress, Destination: &ServerConfig.BindAddress,
}, },
cli.IntFlag{ cli.IntFlag{
...@@ -95,7 +96,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -95,7 +96,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
}, },
cli.StringFlag{ cli.StringFlag{
Name: "data-dir,d", Name: "data-dir,d",
Usage: "(data) Folder to hold state default /var/lib/rancher/k3s or ${HOME}/.rancher/k3s if not root", Usage: "(data) Folder to hold state default /var/lib/rancher/" + version.Program + " or ${HOME}/.rancher/" + version.Program + " if not root",
Destination: &ServerConfig.DataDir, Destination: &ServerConfig.DataDir,
}, },
cli.StringFlag{ cli.StringFlag{
...@@ -132,25 +133,25 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -132,25 +133,25 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
Name: "token,t", Name: "token,t",
Usage: "(cluster) Shared secret used to join a server or agent to a cluster", Usage: "(cluster) Shared secret used to join a server or agent to a cluster",
Destination: &ServerConfig.Token, Destination: &ServerConfig.Token,
EnvVar: "K3S_TOKEN", EnvVar: version.ProgramUpper + "_TOKEN",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "token-file", Name: "token-file",
Usage: "(cluster) File containing the cluster-secret/token", Usage: "(cluster) File containing the cluster-secret/token",
Destination: &ServerConfig.TokenFile, Destination: &ServerConfig.TokenFile,
EnvVar: "K3S_TOKEN_FILE", EnvVar: version.ProgramUpper + "_TOKEN_FILE",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "write-kubeconfig,o", Name: "write-kubeconfig,o",
Usage: "(client) Write kubeconfig for admin client to this file", Usage: "(client) Write kubeconfig for admin client to this file",
Destination: &ServerConfig.KubeConfigOutput, Destination: &ServerConfig.KubeConfigOutput,
EnvVar: "K3S_KUBECONFIG_OUTPUT", EnvVar: version.ProgramUpper + "_KUBECONFIG_OUTPUT",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "write-kubeconfig-mode", Name: "write-kubeconfig-mode",
Usage: "(client) Write kubeconfig with this mode", Usage: "(client) Write kubeconfig with this mode",
Destination: &ServerConfig.KubeConfigMode, Destination: &ServerConfig.KubeConfigMode,
EnvVar: "K3S_KUBECONFIG_MODE", EnvVar: version.ProgramUpper + "_KUBECONFIG_MODE",
}, },
cli.StringSliceFlag{ cli.StringSliceFlag{
Name: "kube-apiserver-arg", Name: "kube-apiserver-arg",
...@@ -176,25 +177,25 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -176,25 +177,25 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
Name: "datastore-endpoint", Name: "datastore-endpoint",
Usage: "(db) Specify etcd, Mysql, Postgres, or Sqlite (default) data source name", Usage: "(db) Specify etcd, Mysql, Postgres, or Sqlite (default) data source name",
Destination: &ServerConfig.DatastoreEndpoint, Destination: &ServerConfig.DatastoreEndpoint,
EnvVar: "K3S_DATASTORE_ENDPOINT", EnvVar: version.ProgramUpper + "_DATASTORE_ENDPOINT",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "datastore-cafile", Name: "datastore-cafile",
Usage: "(db) TLS Certificate Authority file used to secure datastore backend communication", Usage: "(db) TLS Certificate Authority file used to secure datastore backend communication",
Destination: &ServerConfig.DatastoreCAFile, Destination: &ServerConfig.DatastoreCAFile,
EnvVar: "K3S_DATASTORE_CAFILE", EnvVar: version.ProgramUpper + "_DATASTORE_CAFILE",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "datastore-certfile", Name: "datastore-certfile",
Usage: "(db) TLS certification file used to secure datastore backend communication", Usage: "(db) TLS certification file used to secure datastore backend communication",
Destination: &ServerConfig.DatastoreCertFile, Destination: &ServerConfig.DatastoreCertFile,
EnvVar: "K3S_DATASTORE_CERTFILE", EnvVar: version.ProgramUpper + "_DATASTORE_CERTFILE",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "datastore-keyfile", Name: "datastore-keyfile",
Usage: "(db) TLS key file used to secure datastore backend communication", Usage: "(db) TLS key file used to secure datastore backend communication",
Destination: &ServerConfig.DatastoreKeyFile, Destination: &ServerConfig.DatastoreKeyFile,
EnvVar: "K3S_DATASTORE_KEYFILE", EnvVar: version.ProgramUpper + "_DATASTORE_KEYFILE",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "default-local-storage-path", Name: "default-local-storage-path",
...@@ -212,7 +213,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -212,7 +213,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "disable-cloud-controller", Name: "disable-cloud-controller",
Usage: "(components) Disable k3s default cloud controller manager", Usage: "(components) Disable " + version.Program + " default cloud controller manager",
Destination: &ServerConfig.DisableCCM, Destination: &ServerConfig.DisableCCM,
}, },
cli.BoolFlag{ cli.BoolFlag{
...@@ -222,7 +223,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -222,7 +223,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "disable-network-policy", Name: "disable-network-policy",
Usage: "(components) Disable k3s default network policy controller", Usage: "(components) Disable " + version.Program + " default network policy controller",
Destination: &ServerConfig.DisableNPC, Destination: &ServerConfig.DisableNPC,
}, },
NodeNameFlag, NodeNameFlag,
...@@ -250,32 +251,32 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -250,32 +251,32 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
Name: "agent-token", Name: "agent-token",
Usage: "(experimental/cluster) Shared secret used to join agents to the cluster, but not servers", Usage: "(experimental/cluster) Shared secret used to join agents to the cluster, but not servers",
Destination: &ServerConfig.AgentToken, Destination: &ServerConfig.AgentToken,
EnvVar: "K3S_AGENT_TOKEN", EnvVar: version.ProgramUpper + "_AGENT_TOKEN",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "agent-token-file", Name: "agent-token-file",
Usage: "(experimental/cluster) File containing the agent secret", Usage: "(experimental/cluster) File containing the agent secret",
Destination: &ServerConfig.AgentTokenFile, Destination: &ServerConfig.AgentTokenFile,
EnvVar: "K3S_AGENT_TOKEN_FILE", EnvVar: version.ProgramUpper + "_AGENT_TOKEN_FILE",
}, },
cli.StringFlag{ cli.StringFlag{
Name: "server,s", Name: "server,s",
Usage: "(experimental/cluster) Server to connect to, used to join a cluster", Usage: "(experimental/cluster) Server to connect to, used to join a cluster",
EnvVar: "K3S_URL", EnvVar: version.ProgramUpper + "_URL",
Destination: &ServerConfig.ServerURL, Destination: &ServerConfig.ServerURL,
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "cluster-init", Name: "cluster-init",
Hidden: hideDqlite, Hidden: hideDqlite,
Usage: "(experimental/cluster) Initialize new cluster master", Usage: "(experimental/cluster) Initialize new cluster master",
EnvVar: "K3S_CLUSTER_INIT", EnvVar: version.ProgramUpper + "_CLUSTER_INIT",
Destination: &ServerConfig.ClusterInit, Destination: &ServerConfig.ClusterInit,
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "cluster-reset", Name: "cluster-reset",
Hidden: hideDqlite, Hidden: hideDqlite,
Usage: "(experimental/cluster) Forget all peers and become a single cluster new cluster master", Usage: "(experimental/cluster) Forget all peers and become a single cluster new cluster master",
EnvVar: "K3S_CLUSTER_RESET", EnvVar: version.ProgramUpper + "_CLUSTER_RESET",
Destination: &ServerConfig.ClusterReset, Destination: &ServerConfig.ClusterReset,
}, },
cli.BoolFlag{ cli.BoolFlag{
...@@ -295,7 +296,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -295,7 +296,7 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
Name: "cluster-secret", Name: "cluster-secret",
Usage: "(deprecated) use --token", Usage: "(deprecated) use --token",
Destination: &ServerConfig.ClusterSecret, Destination: &ServerConfig.ClusterSecret,
EnvVar: "K3S_CLUSTER_SECRET", EnvVar: version.ProgramUpper + "_CLUSTER_SECRET",
}, },
cli.BoolFlag{ cli.BoolFlag{
Name: "disable-agent", Name: "disable-agent",
......
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
"github.com/rancher/k3s/pkg/rootless" "github.com/rancher/k3s/pkg/rootless"
"github.com/rancher/k3s/pkg/server" "github.com/rancher/k3s/pkg/server"
"github.com/rancher/k3s/pkg/token" "github.com/rancher/k3s/pkg/token"
"github.com/rancher/k3s/pkg/version"
"github.com/rancher/wrangler/pkg/signals" "github.com/rancher/wrangler/pkg/signals"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
...@@ -209,7 +210,7 @@ func run(app *cli.Context, cfg *cmds.Server) error { ...@@ -209,7 +210,7 @@ func run(app *cli.Context, cfg *cmds.Server) error {
return errors.Wrapf(err, "Invalid TLS Cipher Suites %s: %v", TLSCipherSuites, err) return errors.Wrapf(err, "Invalid TLS Cipher Suites %s: %v", TLSCipherSuites, err)
} }
logrus.Info("Starting k3s ", app.App.Version) logrus.Info("Starting "+version.Program+" ", app.App.Version)
notifySocket := os.Getenv("NOTIFY_SOCKET") notifySocket := os.Getenv("NOTIFY_SOCKET")
os.Unsetenv("NOTIFY_SOCKET") os.Unsetenv("NOTIFY_SOCKET")
...@@ -220,7 +221,7 @@ func run(app *cli.Context, cfg *cmds.Server) error { ...@@ -220,7 +221,7 @@ func run(app *cli.Context, cfg *cmds.Server) error {
go func() { go func() {
<-serverConfig.ControlConfig.Runtime.APIServerReady <-serverConfig.ControlConfig.Runtime.APIServerReady
logrus.Info("k3s is up and running") logrus.Info("" + version.Program + " is up and running")
if notifySocket != "" { if notifySocket != "" {
os.Setenv("NOTIFY_SOCKET", notifySocket) os.Setenv("NOTIFY_SOCKET", notifySocket)
systemd.SdNotify(true, "READY=1\n") systemd.SdNotify(true, "READY=1\n")
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"io" "io"
"github.com/rancher/k3s/pkg/version"
"github.com/rancher/wrangler-api/pkg/generated/controllers/core" "github.com/rancher/wrangler-api/pkg/generated/controllers/core"
coreclient "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1" coreclient "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
"github.com/rancher/wrangler/pkg/start" "github.com/rancher/wrangler/pkg/start"
...@@ -15,7 +16,7 @@ type k3s struct { ...@@ -15,7 +16,7 @@ type k3s struct {
} }
func init() { func init() {
cloudprovider.RegisterCloudProvider("k3s", func(config io.Reader) (cloudprovider.Interface, error) { cloudprovider.RegisterCloudProvider(version.Program, func(config io.Reader) (cloudprovider.Interface, error) {
return &k3s{}, nil return &k3s{}, nil
}) })
} }
...@@ -49,7 +50,7 @@ func (k *k3s) Routes() (cloudprovider.Routes, bool) { ...@@ -49,7 +50,7 @@ func (k *k3s) Routes() (cloudprovider.Routes, bool) {
} }
func (k *k3s) ProviderName() string { func (k *k3s) ProviderName() string {
return "k3s" return version.Program
} }
func (k *k3s) HasClusterID() bool { func (k *k3s) HasClusterID() bool {
......
...@@ -4,16 +4,17 @@ import ( ...@@ -4,16 +4,17 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/rancher/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
cloudprovider "k8s.io/cloud-provider" cloudprovider "k8s.io/cloud-provider"
) )
const ( var (
InternalIPLabel = "k3s.io/internal-ip" InternalIPLabel = version.Program + ".io/internal-ip"
ExternalIPLabel = "k3s.io/external-ip" ExternalIPLabel = version.Program + ".io/external-ip"
HostnameLabel = "k3s.io/hostname" HostnameLabel = version.Program + ".io/hostname"
) )
func (k *k3s) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error { func (k *k3s) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
...@@ -45,7 +46,7 @@ func (k *k3s) InstanceType(ctx context.Context, name types.NodeName) (string, er ...@@ -45,7 +46,7 @@ func (k *k3s) InstanceType(ctx context.Context, name types.NodeName) (string, er
if err != nil { if err != nil {
return "", err return "", err
} }
return "k3s", nil return version.Program, nil
} }
func (k *k3s) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) { func (k *k3s) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
......
...@@ -9,18 +9,23 @@ import ( ...@@ -9,18 +9,23 @@ import (
"github.com/rancher/k3s/pkg/bootstrap" "github.com/rancher/k3s/pkg/bootstrap"
"github.com/rancher/k3s/pkg/clientaccess" "github.com/rancher/k3s/pkg/clientaccess"
"github.com/rancher/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
func (c *Cluster) Join(ctx context.Context) error { func (c *Cluster) Bootstrap(ctx context.Context) error {
runJoin, err := c.shouldJoin() if err := c.assignManagedDriver(ctx); err != nil {
return err
}
runBootstrap, err := c.shouldBootstrapLoad()
if err != nil { if err != nil {
return err return err
} }
c.runJoin = runJoin c.shouldBootstrap = runBootstrap
if runJoin { if runBootstrap {
if err := c.join(ctx); err != nil { if err := c.bootstrap(ctx); err != nil {
return err return err
} }
} }
...@@ -28,38 +33,48 @@ func (c *Cluster) Join(ctx context.Context) error { ...@@ -28,38 +33,48 @@ func (c *Cluster) Join(ctx context.Context) error {
return nil return nil
} }
func (c *Cluster) shouldJoin() (bool, error) { func (c *Cluster) shouldBootstrapLoad() (bool, error) {
dqlite := c.dqliteEnabled() if c.managedDB != nil {
if dqlite {
c.runtime.HTTPBootstrap = true c.runtime.HTTPBootstrap = true
if c.config.JoinURL == "" { if c.config.JoinURL == "" {
return false, nil return false, nil
} }
token, err := clientaccess.NormalizeAndValidateTokenForUser(c.config.JoinURL, c.config.Token, "server")
if err != nil {
return false, err
}
info, err := clientaccess.ParseAndValidateToken(c.config.JoinURL, token)
if err != nil {
return false, err
}
c.clientAccessInfo = info
} }
stamp := c.joinStamp() stamp := c.bootstrapStamp()
if _, err := os.Stat(stamp); err == nil { if _, err := os.Stat(stamp); err == nil {
logrus.Info("Cluster bootstrap already complete") logrus.Info("Cluster bootstrap already complete")
return false, nil return false, nil
} }
if dqlite && c.config.Token == "" { if c.managedDB != nil && c.config.Token == "" {
return false, fmt.Errorf("K3S_TOKEN is required to join a cluster") return false, fmt.Errorf("K3S_TOKEN is required to join a cluster")
} }
return true, nil return true, nil
} }
func (c *Cluster) joined() error { func (c *Cluster) bootstrapped() error {
if err := os.MkdirAll(filepath.Dir(c.joinStamp()), 0700); err != nil { if err := os.MkdirAll(filepath.Dir(c.bootstrapStamp()), 0700); err != nil {
return err return err
} }
if _, err := os.Stat(c.joinStamp()); err == nil { if _, err := os.Stat(c.bootstrapStamp()); err == nil {
return nil return nil
} }
f, err := os.Create(c.joinStamp()) f, err := os.Create(c.bootstrapStamp())
if err != nil { if err != nil {
return err return err
} }
...@@ -67,19 +82,8 @@ func (c *Cluster) joined() error { ...@@ -67,19 +82,8 @@ func (c *Cluster) joined() error {
return f.Close() return f.Close()
} }
func (c *Cluster) httpJoin() error { func (c *Cluster) httpBootstrap() error {
token, err := clientaccess.NormalizeAndValidateTokenForUser(c.config.JoinURL, c.config.Token, "server") content, err := clientaccess.Get("/v1-"+version.Program+"/server-bootstrap", c.clientAccessInfo)
if err != nil {
return err
}
info, err := clientaccess.ParseAndValidateToken(c.config.JoinURL, token)
if err != nil {
return err
}
c.clientAccessInfo = info
content, err := clientaccess.Get("/v1-k3s/server-bootstrap", info)
if err != nil { if err != nil {
return err return err
} }
...@@ -87,20 +91,20 @@ func (c *Cluster) httpJoin() error { ...@@ -87,20 +91,20 @@ func (c *Cluster) httpJoin() error {
return bootstrap.Read(bytes.NewBuffer(content), &c.runtime.ControlRuntimeBootstrap) return bootstrap.Read(bytes.NewBuffer(content), &c.runtime.ControlRuntimeBootstrap)
} }
func (c *Cluster) join(ctx context.Context) error { func (c *Cluster) bootstrap(ctx context.Context) error {
c.joining = true c.joining = true
if c.runtime.HTTPBootstrap { if c.runtime.HTTPBootstrap {
return c.httpJoin() return c.httpBootstrap()
} }
if err := c.storageJoin(ctx); err != nil { if err := c.storageBootstrap(ctx); err != nil {
return err return err
} }
return nil return nil
} }
func (c *Cluster) joinStamp() string { func (c *Cluster) bootstrapStamp() string {
return filepath.Join(c.config.DataDir, "db/joined-"+keyHash(c.config.Token)) return filepath.Join(c.config.DataDir, "db/joined-"+keyHash(c.config.Token))
} }
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rancher/k3s/pkg/clientaccess" "github.com/rancher/k3s/pkg/clientaccess"
"github.com/rancher/k3s/pkg/cluster/managed"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/kine/pkg/client" "github.com/rancher/kine/pkg/client"
"github.com/rancher/kine/pkg/endpoint" "github.com/rancher/kine/pkg/endpoint"
...@@ -15,8 +16,8 @@ type Cluster struct { ...@@ -15,8 +16,8 @@ type Cluster struct {
clientAccessInfo *clientaccess.Info clientAccessInfo *clientaccess.Info
config *config.Control config *config.Control
runtime *config.ControlRuntime runtime *config.ControlRuntime
db interface{} managedDB managed.Driver
runJoin bool shouldBootstrap bool
storageStarted bool storageStarted bool
etcdConfig endpoint.ETCDConfig etcdConfig endpoint.ETCDConfig
joining bool joining bool
...@@ -24,34 +25,33 @@ type Cluster struct { ...@@ -24,34 +25,33 @@ type Cluster struct {
storageClient client.Client storageClient client.Client
} }
func (c *Cluster) Start(ctx context.Context) error { func (c *Cluster) Start(ctx context.Context) (<-chan struct{}, error) {
if err := c.startClusterAndHTTPS(ctx); err != nil { if err := c.initClusterAndHTTPS(ctx); err != nil {
return errors.Wrap(err, "start cluster and https") return nil, errors.Wrap(err, "start cluster and https")
} }
if c.runJoin { if err := c.start(ctx); err != nil {
if err := c.postJoin(ctx); err != nil { return nil, errors.Wrap(err, "start cluster and https")
return errors.Wrap(err, "post join")
}
} }
if err := c.testClusterDB(ctx); err != nil { ready, err := c.testClusterDB(ctx)
return err if err != nil {
return nil, err
} }
if c.saveBootstrap { if c.saveBootstrap {
if err := c.save(ctx); err != nil { if err := c.save(ctx); err != nil {
return err return nil, err
} }
} }
if c.runJoin { if c.shouldBootstrap {
if err := c.joined(); err != nil { if err := c.bootstrapped(); err != nil {
return err return nil, err
} }
} }
return c.startStorage(ctx) return ready, c.startStorage(ctx)
} }
func (c *Cluster) startStorage(ctx context.Context) error { func (c *Cluster) startStorage(ctx context.Context) error {
......
// +build dqlite
package cluster
import (
"context"
"crypto/tls"
"encoding/json"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/canonical/go-dqlite/client"
"github.com/rancher/dynamiclistener/factory"
"github.com/rancher/k3s/pkg/clientaccess"
"github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/dqlite"
"github.com/rancher/kine/pkg/endpoint"
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
"github.com/sirupsen/logrus"
)
func (c *Cluster) testClusterDB(ctx context.Context) error {
if !c.dqliteEnabled() {
return nil
}
dqlite := c.db.(*dqlite.DQLite)
for {
if err := dqlite.Test(ctx); err != nil {
logrus.Infof("Failed to test dqlite connection: %v", err)
} else {
return nil
}
select {
case <-time.After(2 * time.Second):
case <-ctx.Done():
return ctx.Err()
}
}
}
func (c *Cluster) initClusterDB(ctx context.Context, l net.Listener, handler http.Handler) (net.Listener, http.Handler, error) {
if !c.dqliteEnabled() {
return l, handler, nil
}
dqlite := dqlite.New(c.config.DataDir, c.config.AdvertiseIP, c.config.AdvertisePort, func() v1.NodeController {
if c.runtime.Core == nil {
return nil
}
return c.runtime.Core.Core().V1().Node()
})
certs, err := toGetCerts(c.runtime)
if err != nil {
return nil, nil, err
}
handler, err = dqlite.Start(ctx, c.config.ClusterInit, c.config.ClusterReset, certs, handler)
if err != nil {
return nil, nil, err
}
if c.config.ClusterReset {
if err := dqlite.Reset(ctx); err == nil {
logrus.Info("Cluster reset successful, now rejoin members")
os.Exit(0)
} else {
logrus.Fatalf("Cluster reset failed: %v", err)
}
}
c.db = dqlite
if !strings.HasPrefix(c.config.Datastore.Endpoint, "dqlite://") {
c.config.Datastore = endpoint.Config{
Endpoint: dqlite.StorageEndpoint,
}
}
return l, handler, err
}
func (c *Cluster) dqliteEnabled() bool {
stamp := filepath.Join(dqlite.GetDBDir(c.config.DataDir))
if _, err := os.Stat(stamp); err == nil {
return true
}
driver, _ := endpoint.ParseStorageEndpoint(c.config.Datastore.Endpoint)
if driver == endpoint.DQLiteBackend {
return true
}
return c.config.Datastore.Endpoint == "" && (c.config.ClusterInit || (c.config.Token != "" && c.config.JoinURL != ""))
}
func (c *Cluster) postJoin(ctx context.Context) error {
if !c.dqliteEnabled() {
return nil
}
resp, err := clientaccess.Get("/db/info", c.clientAccessInfo)
if err != nil {
return err
}
dqlite := c.db.(*dqlite.DQLite)
var nodes []client.NodeInfo
if err := json.Unmarshal(resp, &nodes); err != nil {
return err
}
return dqlite.Join(ctx, nodes)
}
func toGetCerts(runtime *config.ControlRuntime) (*dqlite.Certs, error) {
clientCA, _, err := factory.LoadCerts(runtime.ClientCA, runtime.ClientCAKey)
if err != nil {
return nil, err
}
ca, _, err := factory.LoadCerts(runtime.ServerCA, runtime.ServerCAKey)
if err != nil {
return nil, err
}
clientCert, err := tls.LoadX509KeyPair(runtime.ClientKubeAPICert, runtime.ClientKubeAPIKey)
if err != nil {
return nil, err
}
return &dqlite.Certs{
ServerTrust: ca,
ClientTrust: clientCA,
ClientCert: clientCert,
}, nil
}
// +build !no_etcd
package cluster
import (
"github.com/rancher/k3s/pkg/cluster/managed"
"github.com/rancher/k3s/pkg/etcd"
)
func init() {
managed.RegisterDriver(&etcd.ETCD{})
}
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"github.com/rancher/dynamiclistener/storage/kubernetes" "github.com/rancher/dynamiclistener/storage/kubernetes"
"github.com/rancher/dynamiclistener/storage/memory" "github.com/rancher/dynamiclistener/storage/memory"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/version"
"github.com/rancher/wrangler-api/pkg/generated/controllers/core" "github.com/rancher/wrangler-api/pkg/generated/controllers/core"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
...@@ -30,8 +31,8 @@ func (c *Cluster) newListener(ctx context.Context) (net.Listener, http.Handler, ...@@ -30,8 +31,8 @@ func (c *Cluster) newListener(ctx context.Context) (net.Listener, http.Handler,
storage := tlsStorage(ctx, c.config.DataDir, c.runtime) storage := tlsStorage(ctx, c.config.DataDir, c.runtime)
return dynamiclistener.NewListener(tcp, storage, cert, key, dynamiclistener.Config{ return dynamiclistener.NewListener(tcp, storage, cert, key, dynamiclistener.Config{
CN: "k3s", CN: version.Program,
Organization: []string{"k3s"}, Organization: []string{version.Program},
TLSConfig: tls.Config{ TLSConfig: tls.Config{
ClientAuth: tls.RequestClientCert, ClientAuth: tls.RequestClientCert,
MinVersion: c.config.TLSMinVersion, MinVersion: c.config.TLSMinVersion,
...@@ -41,7 +42,7 @@ func (c *Cluster) newListener(ctx context.Context) (net.Listener, http.Handler, ...@@ -41,7 +42,7 @@ func (c *Cluster) newListener(ctx context.Context) (net.Listener, http.Handler,
}) })
} }
func (c *Cluster) startClusterAndHTTPS(ctx context.Context) error { func (c *Cluster) initClusterAndHTTPS(ctx context.Context) error {
l, handler, err := c.newListener(ctx) l, handler, err := c.newListener(ctx)
if err != nil { if err != nil {
return err return err
...@@ -79,5 +80,5 @@ func tlsStorage(ctx context.Context, dataDir string, runtime *config.ControlRunt ...@@ -79,5 +80,5 @@ func tlsStorage(ctx context.Context, dataDir string, runtime *config.ControlRunt
cache := memory.NewBacked(fileStorage) cache := memory.NewBacked(fileStorage)
return kubernetes.New(ctx, func() *core.Factory { return kubernetes.New(ctx, func() *core.Factory {
return runtime.Core return runtime.Core
}, "kube-system", "k3s-serving", cache) }, "kube-system", ""+version.Program+"-serving", cache)
} }
package cluster
import (
"context"
"net"
"net/http"
"strings"
"time"
"github.com/rancher/k3s/pkg/cluster/managed"
"github.com/rancher/kine/pkg/endpoint"
"github.com/sirupsen/logrus"
)
func (c *Cluster) testClusterDB(ctx context.Context) (<-chan struct{}, error) {
result := make(chan struct{})
if c.managedDB == nil {
close(result)
return result, nil
}
go func() {
defer close(result)
for {
if err := c.managedDB.Test(ctx); err != nil {
logrus.Infof("Failed to test data store connection: %v", err)
} else {
logrus.Infof("Data store connection OK")
return
}
select {
case <-time.After(5 * time.Second):
case <-ctx.Done():
return
}
}
}()
return result, nil
}
func (c *Cluster) start(ctx context.Context) error {
if c.managedDB == nil {
return nil
}
if c.config.ClusterReset {
return c.managedDB.Reset(ctx)
}
return c.managedDB.Start(ctx, c.clientAccessInfo)
}
func (c *Cluster) initClusterDB(ctx context.Context, l net.Listener, handler http.Handler) (net.Listener, http.Handler, error) {
if c.managedDB == nil {
return l, handler, nil
}
if !strings.HasPrefix(c.config.Datastore.Endpoint, c.managedDB.EndpointName()+"://") {
c.config.Datastore = endpoint.Config{
Endpoint: c.managedDB.EndpointName(),
}
}
return c.managedDB.Register(ctx, c.config, l, handler)
}
func (c *Cluster) assignManagedDriver(ctx context.Context) error {
for _, driver := range managed.Registered() {
if ok, err := driver.IsInitialized(ctx, c.config); err != nil {
return err
} else if ok {
c.managedDB = driver
return nil
}
}
endpointType := strings.SplitN(c.config.Datastore.Endpoint, ":", 2)[0]
for _, driver := range managed.Registered() {
if endpointType == driver.EndpointName() {
c.managedDB = driver
return nil
}
}
if c.config.Datastore.Endpoint == "" && (c.config.ClusterInit || (c.config.Token != "" && c.config.JoinURL != "")) {
for _, driver := range managed.Registered() {
if driver.EndpointName() == managed.Default() {
c.managedDB = driver
return nil
}
}
}
return nil
}
package managed
import (
"context"
"net"
"net/http"
"github.com/rancher/k3s/pkg/clientaccess"
"github.com/rancher/k3s/pkg/daemons/config"
)
var (
defaultDriver string
drivers []Driver
)
type Driver interface {
IsInitialized(ctx context.Context, config *config.Control) (bool, error)
Register(ctx context.Context, config *config.Control, l net.Listener, handler http.Handler) (net.Listener, http.Handler, error)
Reset(ctx context.Context) error
Start(ctx context.Context, clientAccess *clientaccess.Info) error
Test(ctx context.Context) error
EndpointName() string
}
func RegisterDriver(d Driver) {
drivers = append(drivers, d)
}
func Registered() []Driver {
return drivers
}
func Default() string {
if defaultDriver == "" && len(drivers) == 1 {
return drivers[0].EndpointName()
}
return defaultDriver
}
// +build !dqlite
package cluster
import (
"context"
"net"
"net/http"
)
func (c *Cluster) testClusterDB(ctx context.Context) error {
return nil
}
func (c *Cluster) initClusterDB(ctx context.Context, l net.Listener, handler http.Handler) (net.Listener, http.Handler, error) {
return l, handler, nil
}
func (c *Cluster) postJoin(ctx context.Context) error {
return nil
}
func (c *Cluster) dqliteEnabled() bool {
return false
}
...@@ -22,7 +22,7 @@ func (c *Cluster) save(ctx context.Context) error { ...@@ -22,7 +22,7 @@ func (c *Cluster) save(ctx context.Context) error {
return c.storageClient.Create(ctx, storageKey(c.config.Token), data) return c.storageClient.Create(ctx, storageKey(c.config.Token), data)
} }
func (c *Cluster) storageJoin(ctx context.Context) error { func (c *Cluster) storageBootstrap(ctx context.Context) error {
if err := c.startStorage(ctx); err != nil { if err := c.startStorage(ctx); err != nil {
return err return err
} }
......
package config package config
import ( import (
"context"
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net" "net"
...@@ -132,6 +133,10 @@ type Control struct { ...@@ -132,6 +133,10 @@ type Control struct {
} }
type ControlRuntimeBootstrap struct { type ControlRuntimeBootstrap struct {
ETCDServerCA string
ETCDServerCAKey string
ETCDPeerCA string
ETCDPeerCAKey string
ServerCA string ServerCA string
ServerCAKey string ServerCAKey string
ClientCA string ClientCA string
...@@ -147,8 +152,10 @@ type ControlRuntimeBootstrap struct { ...@@ -147,8 +152,10 @@ type ControlRuntimeBootstrap struct {
type ControlRuntime struct { type ControlRuntime struct {
ControlRuntimeBootstrap ControlRuntimeBootstrap
HTTPBootstrap bool HTTPBootstrap bool
APIServerReady <-chan struct{} APIServerReady <-chan struct{}
ETCDReady <-chan struct{}
ClusterControllerStart func(ctx context.Context) error
ClientKubeAPICert string ClientKubeAPICert string
ClientKubeAPIKey string ClientKubeAPIKey string
...@@ -186,6 +193,13 @@ type ControlRuntime struct { ...@@ -186,6 +193,13 @@ type ControlRuntime struct {
ClientK3sControllerCert string ClientK3sControllerCert string
ClientK3sControllerKey string ClientK3sControllerKey string
ServerETCDCert string
ServerETCDKey string
PeerServerClientETCDCert string
PeerServerClientETCDKey string
ClientETCDCert string
ClientETCDKey string
Core *core.Factory Core *core.Factory
} }
......
...@@ -45,7 +45,8 @@ func (Embedded) KubeProxy(args []string) error { ...@@ -45,7 +45,8 @@ func (Embedded) KubeProxy(args []string) error {
return nil return nil
} }
func (Embedded) APIServer(ctx context.Context, args []string) (authenticator.Request, http.Handler, error) { func (Embedded) APIServer(ctx context.Context, etcdReady <-chan struct{}, args []string) (authenticator.Request, http.Handler, error) {
<-etcdReady
command := app.NewAPIServerCommand(ctx.Done()) command := app.NewAPIServerCommand(ctx.Done())
command.SetArgs(args) command.SetArgs(args)
......
// +build !no_embedded_executor
package executor
import (
"github.com/sirupsen/logrus"
"go.etcd.io/etcd/embed"
)
func (e Embedded) CurrentETCDOptions() (InitialOptions, error) {
return InitialOptions{}, nil
}
func (e Embedded) ETCD(args ETCDConfig) error {
configFile, err := args.ToConfigFile()
if err != nil {
return err
}
cfg, err := embed.ConfigFromFile(configFile)
if err != nil {
return err
}
etcd, err := embed.StartEtcd(cfg)
if err != nil {
return nil
}
go func() {
err := <-etcd.Err()
logrus.Fatalf("etcd exited: %v", err)
}()
return nil
}
...@@ -2,22 +2,78 @@ package executor ...@@ -2,22 +2,78 @@ package executor
import ( import (
"context" "context"
"io/ioutil"
"net/http" "net/http"
"os"
"path/filepath"
"sigs.k8s.io/yaml"
"k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator"
) )
var (
executor Executor
)
type Executor interface { type Executor interface {
Kubelet(args []string) error Kubelet(args []string) error
KubeProxy(args []string) error KubeProxy(args []string) error
APIServer(ctx context.Context, args []string) (authenticator.Request, http.Handler, error) APIServer(ctx context.Context, etcdReady <-chan struct{}, args []string) (authenticator.Request, http.Handler, error)
Scheduler(apiReady <-chan struct{}, args []string) error Scheduler(apiReady <-chan struct{}, args []string) error
ControllerManager(apiReady <-chan struct{}, args []string) error ControllerManager(apiReady <-chan struct{}, args []string) error
CurrentETCDOptions() (InitialOptions, error)
ETCD(args ETCDConfig) error
} }
var ( type ETCDConfig struct {
executor Executor InitialOptions `json:",inline"`
) Name string `json:"name,omitempty"`
ListenClientURLs string `json:"listen-client-urls,omitempty"`
ListenMetricsURLs string `json:"listen-metrics-urls,omitempty"`
ListenPeerURLs string `json:"listen-peer-urls,omitempty"`
AdvertiseClientURLs string `json:"advertise-client-urls,omitempty"`
DataDir string `json:"data-dir,omitempty"`
SnapshotCount int `json:"snapshot-count,omitempty"`
ServerTrust ServerTrust `json:"client-transport-security"`
PeerTrust PeerTrust `json:"peer-transport-security"`
ForceNewCluster bool `json:"force-new-cluster,omitempty"`
HeartbeatInterval int `json:"heartbeat-interval"`
ElectionTimeout int `json:"election-timeout"`
}
type ServerTrust struct {
CertFile string `json:"cert-file"`
KeyFile string `json:"key-file"`
ClientCertAuth bool `json:"client-cert-auth"`
TrustedCAFile string `json:"trusted-ca-file"`
}
type PeerTrust struct {
CertFile string `json:"cert-file"`
KeyFile string `json:"key-file"`
ClientCertAuth bool `json:"client-cert-auth"`
TrustedCAFile string `json:"trusted-ca-file"`
}
type InitialOptions struct {
AdvertisePeerURL string `json:"initial-advertise-peer-urls,omitempty"`
Cluster string `json:"initial-cluster,omitempty"`
State string `json:"initial-cluster-state,omitempty"`
}
func (e ETCDConfig) ToConfigFile() (string, error) {
confFile := filepath.Join(e.DataDir, "config")
bytes, err := yaml.Marshal(&e)
if err != nil {
return "", err
}
if err := os.MkdirAll(e.DataDir, 0700); err != nil {
return "", err
}
return confFile, ioutil.WriteFile(confFile, bytes, 0600)
}
func Set(driver Executor) { func Set(driver Executor) {
executor = driver executor = driver
...@@ -31,8 +87,8 @@ func KubeProxy(args []string) error { ...@@ -31,8 +87,8 @@ func KubeProxy(args []string) error {
return executor.KubeProxy(args) return executor.KubeProxy(args)
} }
func APIServer(ctx context.Context, args []string) (authenticator.Request, http.Handler, error) { func APIServer(ctx context.Context, etcdReady <-chan struct{}, args []string) (authenticator.Request, http.Handler, error) {
return executor.APIServer(ctx, args) return executor.APIServer(ctx, etcdReady, args)
} }
func Scheduler(apiReady <-chan struct{}, args []string) error { func Scheduler(apiReady <-chan struct{}, args []string) error {
...@@ -42,3 +98,11 @@ func Scheduler(apiReady <-chan struct{}, args []string) error { ...@@ -42,3 +98,11 @@ func Scheduler(apiReady <-chan struct{}, args []string) error {
func ControllerManager(apiReady <-chan struct{}, args []string) error { func ControllerManager(apiReady <-chan struct{}, args []string) error {
return executor.ControllerManager(apiReady, args) return executor.ControllerManager(apiReady, args)
} }
func CurrentETCDOptions() (InitialOptions, error) {
return executor.CurrentETCDOptions()
}
func ETCD(args ETCDConfig) error {
return executor.ETCD(args)
}
...@@ -5,14 +5,15 @@ import ( ...@@ -5,14 +5,15 @@ import (
"path/filepath" "path/filepath"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rancher/k3s/pkg/version"
"github.com/rancher/wrangler/pkg/resolvehome" "github.com/rancher/wrangler/pkg/resolvehome"
) )
const ( var (
DefaultDataDir = "/var/lib/rancher/k3s" DefaultDataDir = "/var/lib/rancher/" + version.Program
DefaultHomeDataDir = "${HOME}/.rancher/k3s" DefaultHomeDataDir = "${HOME}/.rancher/" + version.Program
HomeConfig = "${HOME}/.kube/k3s.yaml" HomeConfig = "${HOME}/.kube/" + version.Program + ".yaml"
GlobalConfig = "/etc/rancher/k3s/k3s.yaml" GlobalConfig = "/etc/rancher/" + version.Program + "/" + version.Program + ".yaml"
) )
func Resolve(dataDir string) (string, error) { func Resolve(dataDir string) (string, error) {
......
package client
import (
"context"
"fmt"
"strconv"
"github.com/canonical/go-dqlite/client"
"github.com/canonical/go-dqlite/driver"
controllerv1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/labels"
)
const (
allKey = "_all_"
nodeID = "cluster.k3s.cattle.io/node-id"
nodeAddress = "cluster.k3s.cattle.io/node-address"
master = "node-role.kubernetes.io/master"
)
func Register(ctx context.Context, nodeName string, nodeInfo client.NodeInfo,
nodeStore client.NodeStore, nodes controllerv1.NodeController, opts []client.Option) {
h := &handler{
nodeStore: nodeStore,
nodeController: nodes,
nodeName: nodeName,
id: strconv.FormatUint(nodeInfo.ID, 10),
address: nodeInfo.Address,
ctx: ctx,
opts: opts,
}
nodes.OnChange(ctx, "dqlite-client", h.sync)
nodes.OnRemove(ctx, "dqlite-client", h.onRemove)
}
type handler struct {
nodeStore client.NodeStore
nodeController controllerv1.NodeController
nodeName string
id string
address string
ctx context.Context
opts []client.Option
}
func (h *handler) sync(key string, node *v1.Node) (*v1.Node, error) {
if key == allKey {
return nil, h.updateNodeStore()
}
if node == nil {
return nil, nil
}
if key == h.nodeName {
return h.handleSelf(node)
}
if node.Labels[master] == "true" {
h.nodeController.Enqueue(allKey)
}
return node, nil
}
func (h *handler) ensureExists(address string) error {
c, err := client.FindLeader(h.ctx, h.nodeStore, h.opts...)
if err == driver.ErrNoAvailableLeader {
logrus.Fatalf("no dqlite leader found: %v", err)
} else if err != nil {
return err
}
defer c.Close()
members, err := c.Cluster(h.ctx)
if err != nil {
return err
}
for _, member := range members {
if member.Address == address {
return nil
}
}
logrus.Fatalf("Address %s is not member of the cluster", address)
return nil
}
func (h *handler) handleSelf(node *v1.Node) (*v1.Node, error) {
if node.Annotations[nodeID] == h.id && node.Annotations[nodeAddress] == h.address {
return node, h.ensureExists(h.address)
}
node = node.DeepCopy()
if node.Annotations == nil {
node.Annotations = map[string]string{}
}
node.Annotations[nodeID] = h.id
node.Annotations[nodeAddress] = h.address
return h.nodeController.Update(node)
}
func (h *handler) onRemove(key string, node *v1.Node) (*v1.Node, error) {
address := node.Annotations[nodeAddress]
if address == "" {
return node, nil
}
return node, h.delete(address)
}
func (h *handler) delete(address string) error {
c, err := client.FindLeader(h.ctx, h.nodeStore, h.opts...)
if err != nil {
return err
}
defer c.Close()
members, err := c.Cluster(h.ctx)
if err != nil {
return err
}
for _, member := range members {
if member.Address == address {
logrus.Infof("Removing %s %d from dqlite", member.Address, member.ID)
return c.Remove(h.ctx, member.ID)
}
}
return nil
}
func (h *handler) updateNodeStore() error {
nodes, err := h.nodeController.Cache().List(labels.SelectorFromSet(labels.Set{
master: "true",
}))
if err != nil {
return err
}
var (
nodeInfos []client.NodeInfo
seen = map[string]bool{}
)
for _, node := range nodes {
address, ok := node.Annotations[nodeAddress]
if !ok {
continue
}
nodeIDStr, ok := node.Annotations[nodeID]
if !ok {
continue
}
id, err := strconv.ParseUint(nodeIDStr, 10, 64)
if err != nil {
logrus.Errorf("invalid %s=%s, must be a number: %v", nodeID, nodeIDStr, err)
continue
}
if !seen[address] {
nodeInfos = append(nodeInfos, client.NodeInfo{
ID: id,
Address: address,
})
seen[address] = true
}
}
if len(nodeInfos) == 0 {
return fmt.Errorf("not setting dqlient NodeStore len to 0")
}
return h.nodeStore.Set(h.ctx, nodeInfos)
}
package dialer
import (
"context"
"crypto/tls"
"fmt"
"net"
"github.com/canonical/go-dqlite/client"
"github.com/rancher/k3s/pkg/dqlite/pipe"
)
func NewHTTPDialer(advertiseAddress, bindAddress string, tls *tls.Config) (client.DialFunc, error) {
d := &dialer{
advertiseAddress: advertiseAddress,
bindAddress: bindAddress,
tls: tls,
}
return d.dial, nil
}
type dialer struct {
advertiseAddress string
bindAddress string
tls *tls.Config
}
func (d *dialer) dial(ctx context.Context, address string) (net.Conn, error) {
if address == d.advertiseAddress {
return net.Dial("unix", d.bindAddress)
}
url := fmt.Sprintf("https://%s/db/connect", address)
return pipe.ToHTTP(ctx, url, d.tls)
}
package dqlite
import (
"context"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/canonical/go-dqlite/client"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
func (d *DQLite) Test(ctx context.Context) error {
var ips []string
peers, err := d.NodeStore.Get(ctx)
if err != nil {
return err
}
for _, peer := range peers {
ips = append(ips, peer.Address)
}
logrus.Infof("Testing connection to peers %v", ips)
if err := d.Join(ctx, nil); err != nil {
return err
}
logrus.Infof("Connection OK to peers %v", ips)
return nil
}
func nodeIDsEqual(testID, currentID uint64) bool {
// this is a test for a bug in v1.0.0. In future versions we don't
// generate node ID higher than 1<<20 so this doesn't matter. But
// basically just ignore the first 32 bits.
return uint32(testID) == uint32(currentID)
}
func (d *DQLite) Join(ctx context.Context, nodes []client.NodeInfo) error {
if len(nodes) > 0 {
if err := d.NodeStore.Set(ctx, nodes); err != nil {
return err
}
}
client, err := client.FindLeader(ctx, d.NodeStore, d.clientOpts...)
if err != nil {
return err
}
defer client.Close()
current, err := client.Cluster(ctx)
if err != nil {
return err
}
nodeID, err := getClusterID(false, d.DataDir)
if err != nil {
return errors.Wrap(err, "get cluster ID")
}
for _, testNode := range current {
if testNode.Address == d.NodeInfo.Address {
if !nodeIDsEqual(testNode.ID, nodeID) {
if err := d.node.Close(); err != nil {
return errors.Wrap(err, "node close for id reset")
}
if err := writeClusterID(testNode.ID, d.DataDir); err != nil {
return errors.Wrap(err, "restart node to reset ID")
}
logrus.Fatalf("resetting node ID from %d to %d, please restart", nodeID, testNode.ID)
}
return nil
} else if nodeIDsEqual(testNode.ID, nodeID) {
deleteClusterID(d.DataDir)
logrus.Fatalf("node ID %d is in use, please restart", nodeID)
}
}
if found, err := cleanDir(d.DataDir, true); err != nil {
return err
} else if found {
if err := d.node.Close(); err != nil {
return errors.Wrap(err, "node close for cleaning")
}
_, _ = cleanDir(d.DataDir, false)
return fmt.Errorf("cleaned DB directory, now restart and join")
}
logrus.Infof("Joining dqlite cluster as address=%s, id=%d", d.NodeInfo.Address, d.NodeInfo.ID)
return client.Add(ctx, d.NodeInfo)
}
func cleanDir(dataDir string, check bool) (bool, error) {
dbDir := GetDBDir(dataDir)
backupDir := filepath.Join(dbDir, fmt.Sprintf(".backup-%d", time.Now().Unix()))
files, err := ioutil.ReadDir(dbDir)
if err != nil {
return false, errors.Wrap(err, "cleaning dqlite DB dir")
}
for _, file := range files {
if file.IsDir() || strings.HasPrefix(file.Name(), ".") || ignoreFile[file.Name()] {
continue
}
if check {
return true, nil
}
if err := os.MkdirAll(backupDir, 0700); err != nil {
return false, errors.Wrapf(err, "creating backup dir %s", backupDir)
}
oldName := filepath.Join(dbDir, file.Name())
newName := filepath.Join(backupDir, file.Name())
logrus.Infof("Backing up %s => %s", oldName, newName)
if err := os.Rename(oldName, newName); err != nil {
return false, errors.Wrapf(err, "backup %s", oldName)
}
}
return false, nil
}
package dqlite
import (
"strings"
"github.com/canonical/go-dqlite/client"
"github.com/sirupsen/logrus"
)
func log() client.LogFunc {
return func(level client.LogLevel, s string, i ...interface{}) {
switch level {
case client.LogDebug:
logrus.Debugf(s, i...)
case client.LogError:
logrus.Errorf(s, i...)
case client.LogInfo:
if strings.HasPrefix(s, "connected") {
logrus.Debugf(s, i...)
} else {
logrus.Infof(s, i...)
}
case client.LogWarn:
logrus.Warnf(s, i...)
}
}
}
package pipe
import (
"bufio"
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"github.com/pkg/errors"
)
func ToHTTP(ctx context.Context, url string, tlsConfig *tls.Config) (net.Conn, error) {
request, err := http.NewRequest(http.MethodPost, url, nil)
if err != nil {
return nil, err
}
request = request.WithContext(ctx)
netDial := &net.Dialer{}
if deadline, ok := ctx.Deadline(); ok {
netDial.Deadline = deadline
}
conn, err := tls.DialWithDialer(netDial, "tcp", request.URL.Host, tlsConfig)
if err != nil {
return nil, errors.Wrap(err, "tls dial")
}
err = request.Write(conn)
if err != nil {
return nil, errors.Wrap(err, "request write")
}
response, err := http.ReadResponse(bufio.NewReader(conn), request)
if err != nil {
return nil, errors.Wrap(err, "read request")
}
if response.StatusCode != http.StatusSwitchingProtocols {
return nil, fmt.Errorf("expected 101 response, got: %d", response.StatusCode)
}
listener, err := net.Listen("unix", "")
if err != nil {
return nil, errors.Wrap(err, "Failed to create unix listener")
}
defer listener.Close()
if err := Unix(conn, listener.Addr().String()); err != nil {
return nil, err
}
return listener.Accept()
}
package pipe
import (
"io"
"net"
"github.com/lxc/lxd/shared/eagain"
"github.com/sirupsen/logrus"
)
func UnixPiper(srcs <-chan net.Conn, bindAddress string) {
for src := range srcs {
go Unix(src, bindAddress)
}
}
func Unix(src net.Conn, target string) error {
dst, err := net.Dial("unix", target)
if err != nil {
src.Close()
return err
}
Connect(src, dst)
return nil
}
func Connect(src net.Conn, dst net.Conn) {
go func() {
_, err := io.Copy(eagain.Writer{Writer: dst}, eagain.Reader{Reader: src})
if err != nil && err != io.EOF {
logrus.Debugf("copy pipe src->dst closed: %v", err)
}
src.Close()
dst.Close()
}()
go func() {
_, err := io.Copy(eagain.Writer{Writer: src}, eagain.Reader{Reader: dst})
if err != nil {
logrus.Debugf("copy pipe dst->src closed: %v", err)
}
src.Close()
dst.Close()
}()
}
package dqlite
import (
"context"
"net"
"net/http"
"github.com/pkg/errors"
"github.com/rancher/k3s/pkg/dqlite/pipe"
)
var (
upgradeResponse = []byte("HTTP/1.1 101 Switching Protocols\r\nUpgrade: dqlite\r\n\r\n")
)
type proxy struct {
conns chan net.Conn
}
func newProxy(ctx context.Context, bindAddress string) http.Handler {
p := &proxy{
conns: make(chan net.Conn, 100),
}
go func() {
<-ctx.Done()
close(p.conns)
}()
go pipe.UnixPiper(p.conns, bindAddress)
return p
}
func (h *proxy) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
hijacker, ok := rw.(http.Hijacker)
if !ok {
http.Error(rw, "failed to hijack", http.StatusInternalServerError)
return
}
conn, _, err := hijacker.Hijack()
if err != nil {
err := errors.Wrap(err, "Hijack connection")
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
if n, err := conn.Write(upgradeResponse); err != nil || n != len(upgradeResponse) {
conn.Close()
return
}
h.conns <- conn
}
package dqlite
import (
"context"
"github.com/canonical/go-dqlite/client"
"github.com/sirupsen/logrus"
)
func (d *DQLite) Reset(ctx context.Context) error {
logrus.Infof("Resetting cluster to single master")
return d.node.Recover([]client.NodeInfo{
d.NodeInfo,
})
}
package dqlite
import (
"context"
"crypto/x509"
"encoding/json"
"net/http"
"github.com/canonical/go-dqlite"
"github.com/canonical/go-dqlite/client"
"github.com/gorilla/mux"
)
func router(ctx context.Context, next http.Handler, nodeInfo dqlite.NodeInfo, clientCA *x509.Certificate, clientCN string, bindAddress string) http.Handler {
mux := mux.NewRouter()
mux.Handle("/db/connect", newChecker(newProxy(ctx, bindAddress), clientCA, clientCN))
mux.Handle("/db/info", infoHandler(ctx, nodeInfo, bindAddress))
mux.NotFoundHandler = next
return mux
}
func infoHandler(ctx context.Context, nodeInfo dqlite.NodeInfo, bindAddress string) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
client, err := client.New(ctx, bindAddress, client.WithLogFunc(log()))
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
defer client.Close()
info, err := client.Cluster(ctx)
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
rw.Header().Set("Content-Type", "application/json")
json.NewEncoder(rw).Encode(info)
})
}
type checker struct {
next http.Handler
verify x509.VerifyOptions
cn string
}
func newChecker(next http.Handler, ca *x509.Certificate, cn string) http.Handler {
pool := x509.NewCertPool()
pool.AddCert(ca)
return &checker{
next: next,
verify: x509.VerifyOptions{
Roots: pool,
KeyUsages: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
},
DNSName: cn,
},
cn: cn,
}
}
func (c *checker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if !c.check(req) {
http.Error(rw, "unauthorized", http.StatusUnauthorized)
return
}
c.next.ServeHTTP(rw, req)
}
func (c *checker) check(r *http.Request) bool {
for _, cert := range r.TLS.PeerCertificates {
_, err := cert.Verify(c.verify)
if err == nil {
return cert.Subject.CommonName == c.cn
}
}
return false
}
package dqlite
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/canonical/go-dqlite"
"github.com/canonical/go-dqlite/client"
"github.com/pkg/errors"
controllerclient "github.com/rancher/k3s/pkg/dqlite/controller/client"
"github.com/rancher/k3s/pkg/dqlite/dialer"
dqlitedriver "github.com/rancher/kine/pkg/drivers/dqlite"
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/net"
)
const (
PeersFile = "peers.db"
NodeIDFile = "node-id"
)
var (
ignoreFile = map[string]bool{
PeersFile: true,
NodeIDFile: true,
}
)
type Certs struct {
ServerTrust *x509.Certificate
ClientTrust *x509.Certificate
ClientCert tls.Certificate
}
type DQLite struct {
ClientCA string
ClientCAKey string
ClientCert string
ClientCertKey string
ServerCA string
ServerCAKey string
AdvertiseIP string
AdvertisePort int
DataDir string
NodeStore client.NodeStore
NodeInfo client.NodeInfo
node *dqlite.Node
StorageEndpoint string
NodeControllerGetter NodeControllerGetter
clientOpts []client.Option
}
type NodeControllerGetter func() v1.NodeController
func New(dataDir, advertiseIP string, advertisePort int, getter NodeControllerGetter) *DQLite {
return &DQLite{
AdvertiseIP: advertiseIP,
AdvertisePort: advertisePort,
DataDir: dataDir,
NodeControllerGetter: getter,
}
}
func (d *DQLite) Start(ctx context.Context, initCluster, resetCluster bool, certs *Certs, next http.Handler) (http.Handler, error) {
bindAddress := d.getBindAddress()
clientTLSConfig, err := getClientTLSConfig(certs.ClientCert, certs.ServerTrust)
if err != nil {
return nil, err
}
advertise, err := getAdvertiseAddress(d.AdvertiseIP, d.AdvertisePort)
if err != nil {
return nil, errors.Wrap(err, "get advertise address")
}
dial, err := getDialer(advertise, bindAddress, clientTLSConfig)
if err != nil {
return nil, err
}
dqlitedriver.Dialer = dial
dqlitedriver.Logger = log()
d.clientOpts = append(d.clientOpts, client.WithDialFunc(dial), client.WithLogFunc(log()))
nodeInfo, node, err := getNode(d.DataDir, advertise, bindAddress, initCluster, dial)
if err != nil {
return nil, err
}
d.NodeInfo = nodeInfo
d.node = node
go func() {
<-ctx.Done()
node.Close()
}()
if err := d.nodeStore(ctx, initCluster); err != nil {
return nil, err
}
go d.startController(ctx)
if !resetCluster {
if err := node.Start(); err != nil {
return nil, err
}
}
return router(ctx, next, nodeInfo, certs.ClientTrust, "kube-apiserver", bindAddress), nil
}
func (d *DQLite) startController(ctx context.Context) {
for {
if nc := d.NodeControllerGetter(); nc != nil {
if os.Getenv("NODE_NAME") == "" {
logrus.Errorf("--disable-agent is not compatible with dqlite")
} else {
break
}
}
time.Sleep(time.Second)
}
controllerclient.Register(ctx, os.Getenv("NODE_NAME"), d.NodeInfo, d.NodeStore, d.NodeControllerGetter(), d.clientOpts)
}
func (d *DQLite) nodeStore(ctx context.Context, initCluster bool) error {
peerDB := filepath.Join(GetDBDir(d.DataDir), PeersFile)
ns, err := client.DefaultNodeStore(peerDB)
if err != nil {
return err
}
d.NodeStore = ns
d.StorageEndpoint = fmt.Sprintf("dqlite://?peer-file=%s", peerDB)
if initCluster {
if err := dqlitedriver.AddPeers(ctx, d.NodeStore, d.NodeInfo); err != nil {
return err
}
}
return nil
}
func getAdvertiseAddress(advertiseIP string, advertisePort int) (string, error) {
ip := advertiseIP
if ip == "" {
ipAddr, err := net.ChooseHostInterface()
if err != nil {
return "", err
}
ip = ipAddr.String()
}
return fmt.Sprintf("%s:%d", ip, advertisePort), nil
}
func getClientTLSConfig(cert tls.Certificate, ca *x509.Certificate) (*tls.Config, error) {
tlsConfig := &tls.Config{
RootCAs: x509.NewCertPool(),
Certificates: []tls.Certificate{
cert,
},
ServerName: "kubernetes",
}
tlsConfig.RootCAs.AddCert(ca)
return tlsConfig, nil
}
func getDialer(advertiseAddress, bindAddress string, tlsConfig *tls.Config) (client.DialFunc, error) {
return dialer.NewHTTPDialer(advertiseAddress, bindAddress, tlsConfig)
}
func GetDBDir(dataDir string) string {
return filepath.Join(dataDir, "db", "state.dqlite")
}
func getNode(dataDir string, advertiseAddress, bindAddress string, initCluster bool, dial client.DialFunc) (dqlite.NodeInfo, *dqlite.Node, error) {
id, err := getClusterID(initCluster, dataDir)
if err != nil {
return dqlite.NodeInfo{}, nil, errors.Wrap(err, "reading cluster id")
}
dbDir := GetDBDir(dataDir)
node, err := dqlite.New(id, advertiseAddress, dbDir,
dqlite.WithBindAddress(bindAddress),
dqlite.WithDialFunc(dial),
dqlite.WithNetworkLatency(20*time.Millisecond))
return dqlite.NodeInfo{
ID: id,
Address: advertiseAddress,
}, node, err
}
func writeClusterID(id uint64, dataDir string) error {
idFile := filepath.Join(GetDBDir(dataDir), NodeIDFile)
if err := os.MkdirAll(filepath.Dir(idFile), 0700); err != nil {
return err
}
return ioutil.WriteFile(idFile, []byte(strconv.FormatUint(id, 10)), 0644)
}
func deleteClusterID(dataDir string) {
os.Remove(filepath.Join(GetDBDir(dataDir), NodeIDFile))
}
func getClusterID(initCluster bool, dataDir string) (uint64, error) {
idFile := filepath.Join(GetDBDir(dataDir), NodeIDFile)
content, err := ioutil.ReadFile(idFile)
if os.IsNotExist(err) {
content = nil
} else if err != nil {
return 0, err
}
idStr := strings.TrimSpace(string(content))
if idStr == "" {
id := uint64(rand.Intn(1 << 20))
if initCluster {
id = 1
}
return id, writeClusterID(id, dataDir)
}
return strconv.ParseUint(idStr, 10, 64)
}
func (d *DQLite) getBindAddress() string {
// only anonymous works???
return "@" + filepath.Join(GetDBDir(d.DataDir), "dqlite.sock")
}
package etcd
import (
"context"
"os"
"time"
controllerv1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
"github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
)
const (
nodeID = "etcd.k3s.cattle.io/node-name"
nodeAddress = "etcd.k3s.cattle.io/node-address"
master = "node-role.kubernetes.io/master"
etcdRole = "node-role.kubernetes.io/etcd"
)
type NodeControllerGetter func() controllerv1.NodeController
func Register(ctx context.Context, etcd *ETCD, nodes controllerv1.NodeController) {
h := &handler{
etcd: etcd,
nodeController: nodes,
ctx: ctx,
}
nodes.OnChange(ctx, "managed-etcd-controller", h.sync)
nodes.OnRemove(ctx, "managed-etcd-controller", h.onRemove)
}
type handler struct {
etcd *ETCD
nodeController controllerv1.NodeController
ctx context.Context
}
func (h *handler) sync(key string, node *v1.Node) (*v1.Node, error) {
if node == nil {
return nil, nil
}
nodeName := os.Getenv("NODE_NAME")
if nodeName == "" {
logrus.Debug("waiting for node to be assigned for etcd controller")
h.nodeController.EnqueueAfter(key, 5*time.Second)
return node, nil
}
if key == nodeName {
return h.handleSelf(node)
}
return node, nil
}
func (h *handler) handleSelf(node *v1.Node) (*v1.Node, error) {
if node.Annotations[nodeID] == h.etcd.name &&
node.Annotations[nodeAddress] == h.etcd.address &&
node.Labels[etcdRole] == "true" &&
node.Labels[master] == "true" {
return node, nil
}
node = node.DeepCopy()
if node.Annotations == nil {
node.Annotations = map[string]string{}
}
node.Annotations[nodeID] = h.etcd.name
node.Annotations[nodeAddress] = h.etcd.address
node.Labels[etcdRole] = "true"
node.Labels[master] = "true"
return h.nodeController.Update(node)
}
func (h *handler) onRemove(key string, node *v1.Node) (*v1.Node, error) {
if _, ok := node.Labels[etcdRole]; !ok {
return node, nil
}
id := node.Annotations[nodeID]
address := node.Annotations[nodeAddress]
if address == "" {
return node, nil
}
return node, h.etcd.removePeer(h.ctx, id, address)
}
...@@ -9,14 +9,18 @@ import ( ...@@ -9,14 +9,18 @@ import (
"strings" "strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/rancher/k3s/pkg/version"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
) )
var (
NodeArgsAnnotation = version.Program + ".io/node-args"
NodeEnvAnnotation = version.Program + ".io/node-env"
NodeConfigHashAnnotation = version.Program + ".io/node-config-hash"
)
const ( const (
NodeArgsAnnotation = "k3s.io/node-args" OmittedValue = "********"
NodeEnvAnnotation = "k3s.io/node-env"
NodeConfigHashAnnotation = "k3s.io/node-config-hash"
OmittedValue = "********"
) )
func getNodeArgs() (string, error) { func getNodeArgs() (string, error) {
...@@ -47,7 +51,7 @@ func getNodeEnv() (string, error) { ...@@ -47,7 +51,7 @@ func getNodeEnv() (string, error) {
k3sEnv := make(map[string]string) k3sEnv := make(map[string]string)
for _, v := range os.Environ() { for _, v := range os.Environ() {
keyValue := strings.SplitN(v, "=", 2) keyValue := strings.SplitN(v, "=", 2)
if strings.HasPrefix(keyValue[0], "K3S_") { if strings.HasPrefix(keyValue[0], version.ProgramUpper+"_") {
k3sEnv[keyValue[0]] = keyValue[1] k3sEnv[keyValue[0]] = keyValue[1]
} }
} }
...@@ -93,10 +97,10 @@ func SetNodeConfigAnnotations(node *corev1.Node) (bool, error) { ...@@ -93,10 +97,10 @@ func SetNodeConfigAnnotations(node *corev1.Node) (bool, error) {
func isSecret(key string) bool { func isSecret(key string) bool {
secretData := []string{ secretData := []string{
"K3S_TOKEN", version.ProgramUpper + "_TOKEN",
"K3S_DATASTORE_ENDPOINT", version.ProgramUpper + "_DATASTORE_ENDPOINT",
"K3S_AGENT_TOKEN", version.ProgramUpper + "_AGENT_TOKEN",
"K3S_CLUSTER_SECRET", version.ProgramUpper + "_CLUSTER_SECRET",
"--token", "--token",
"-t", "-t",
"--agent-token", "--agent-token",
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/rancher/k3s/pkg/version"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
...@@ -27,7 +28,7 @@ var FakeNodeWithAnnotation = &corev1.Node{ ...@@ -27,7 +28,7 @@ var FakeNodeWithAnnotation = &corev1.Node{
Name: "fakeNode-with-annotation", Name: "fakeNode-with-annotation",
Annotations: map[string]string{ Annotations: map[string]string{
NodeArgsAnnotation: `["server","--no-flannel"]`, NodeArgsAnnotation: `["server","--no-flannel"]`,
NodeEnvAnnotation: `{"K3S_NODE_NAME":"fakeNode-with-annotation"}`, NodeEnvAnnotation: `{"` + version.ProgramUpper + `_NODE_NAME":"fakeNode-with-annotation"}`,
NodeConfigHashAnnotation: "LNQOAOIMOQIBRMEMACW7LYHXUNPZADF6RFGOSPIHJCOS47UVUJAA====", NodeConfigHashAnnotation: "LNQOAOIMOQIBRMEMACW7LYHXUNPZADF6RFGOSPIHJCOS47UVUJAA====",
}, },
}, },
...@@ -40,8 +41,8 @@ func assertEqual(t *testing.T, a interface{}, b interface{}) { ...@@ -40,8 +41,8 @@ func assertEqual(t *testing.T, a interface{}, b interface{}) {
} }
func TestSetEmptyNodeConfigAnnotations(t *testing.T) { func TestSetEmptyNodeConfigAnnotations(t *testing.T) {
os.Args = []string{"k3s", "server", "--no-flannel"} os.Args = []string{version.Program, "server", "--no-flannel"}
os.Setenv("K3S_NODE_NAME", "fakeNode-no-annotation") os.Setenv(version.ProgramUpper+"_NODE_NAME", "fakeNode-no-annotation")
nodeUpdated, err := SetNodeConfigAnnotations(FakeNodeWithNoAnnotation) nodeUpdated, err := SetNodeConfigAnnotations(FakeNodeWithNoAnnotation)
if err != nil { if err != nil {
t.Fatalf("Failed to set node config annotation: %v", err) t.Fatalf("Failed to set node config annotation: %v", err)
...@@ -52,7 +53,7 @@ func TestSetEmptyNodeConfigAnnotations(t *testing.T) { ...@@ -52,7 +53,7 @@ func TestSetEmptyNodeConfigAnnotations(t *testing.T) {
actualArgs := FakeNodeWithNoAnnotation.Annotations[NodeArgsAnnotation] actualArgs := FakeNodeWithNoAnnotation.Annotations[NodeArgsAnnotation]
assertEqual(t, expectedArgs, actualArgs) assertEqual(t, expectedArgs, actualArgs)
expectedEnv := `{"K3S_NODE_NAME":"fakeNode-no-annotation"}` expectedEnv := `{"` + version.ProgramUpper + `_NODE_NAME":"fakeNode-no-annotation"}`
actualEnv := FakeNodeWithNoAnnotation.Annotations[NodeEnvAnnotation] actualEnv := FakeNodeWithNoAnnotation.Annotations[NodeEnvAnnotation]
assertEqual(t, expectedEnv, actualEnv) assertEqual(t, expectedEnv, actualEnv)
...@@ -63,8 +64,8 @@ func TestSetEmptyNodeConfigAnnotations(t *testing.T) { ...@@ -63,8 +64,8 @@ func TestSetEmptyNodeConfigAnnotations(t *testing.T) {
func TestSetExistingNodeConfigAnnotations(t *testing.T) { func TestSetExistingNodeConfigAnnotations(t *testing.T) {
// adding same config // adding same config
os.Args = []string{"k3s", "server", "--no-flannel"} os.Args = []string{version.Program, "server", "--no-flannel"}
os.Setenv("K3S_NODE_NAME", "fakeNode-with-annotation") os.Setenv(version.ProgramUpper+"_NODE_NAME", "fakeNode-with-annotation")
nodeUpdated, err := SetNodeConfigAnnotations(FakeNodeWithAnnotation) nodeUpdated, err := SetNodeConfigAnnotations(FakeNodeWithAnnotation)
if err != nil { if err != nil {
t.Fatalf("Failed to set node config annotation: %v", err) t.Fatalf("Failed to set node config annotation: %v", err)
...@@ -73,7 +74,7 @@ func TestSetExistingNodeConfigAnnotations(t *testing.T) { ...@@ -73,7 +74,7 @@ func TestSetExistingNodeConfigAnnotations(t *testing.T) {
} }
func TestSetArgsWithEqual(t *testing.T) { func TestSetArgsWithEqual(t *testing.T) {
os.Args = []string{"k3s", "server", "--no-flannel", "--write-kubeconfig-mode=777"} os.Args = []string{version.Program, "server", "--no-flannel", "--write-kubeconfig-mode=777"}
os.Setenv("K3S_NODE_NAME", "fakeNode-with-no-annotation") os.Setenv("K3S_NODE_NAME", "fakeNode-with-no-annotation")
nodeUpdated, err := SetNodeConfigAnnotations(FakeNodeWithNoAnnotation) nodeUpdated, err := SetNodeConfigAnnotations(FakeNodeWithNoAnnotation)
if err != nil { if err != nil {
......
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
"github.com/rancher/k3s/pkg/bootstrap" "github.com/rancher/k3s/pkg/bootstrap"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/passwd" "github.com/rancher/k3s/pkg/passwd"
"github.com/rancher/k3s/pkg/version"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
) )
...@@ -26,28 +27,29 @@ const ( ...@@ -26,28 +27,29 @@ const (
) )
func router(serverConfig *config.Control, tunnel http.Handler, ca []byte) http.Handler { func router(serverConfig *config.Control, tunnel http.Handler, ca []byte) http.Handler {
prefix := "/v1-" + version.Program
authed := mux.NewRouter() authed := mux.NewRouter()
authed.Use(authMiddleware(serverConfig, "k3s:agent")) authed.Use(authMiddleware(serverConfig, version.Program+":agent"))
authed.NotFoundHandler = serverConfig.Runtime.Handler authed.NotFoundHandler = serverConfig.Runtime.Handler
authed.Path("/v1-k3s/serving-kubelet.crt").Handler(servingKubeletCert(serverConfig, serverConfig.Runtime.ServingKubeletKey)) authed.Path(prefix + "/serving-kubelet.crt").Handler(servingKubeletCert(serverConfig, serverConfig.Runtime.ServingKubeletKey))
authed.Path("/v1-k3s/client-kubelet.crt").Handler(clientKubeletCert(serverConfig, serverConfig.Runtime.ClientKubeletKey)) authed.Path(prefix + "/client-kubelet.crt").Handler(clientKubeletCert(serverConfig, serverConfig.Runtime.ClientKubeletKey))
authed.Path("/v1-k3s/client-kube-proxy.crt").Handler(fileHandler(serverConfig.Runtime.ClientKubeProxyCert, serverConfig.Runtime.ClientKubeProxyKey)) authed.Path(prefix + "/client-kube-proxy.crt").Handler(fileHandler(serverConfig.Runtime.ClientKubeProxyCert, serverConfig.Runtime.ClientKubeProxyKey))
authed.Path("/v1-k3s/client-k3s-controller.crt").Handler(fileHandler(serverConfig.Runtime.ClientK3sControllerCert, serverConfig.Runtime.ClientK3sControllerKey)) authed.Path(prefix + "/client-" + version.Program + "-controller.crt").Handler(fileHandler(serverConfig.Runtime.ClientK3sControllerCert, serverConfig.Runtime.ClientK3sControllerKey))
authed.Path("/v1-k3s/client-ca.crt").Handler(fileHandler(serverConfig.Runtime.ClientCA)) authed.Path(prefix + "/client-ca.crt").Handler(fileHandler(serverConfig.Runtime.ClientCA))
authed.Path("/v1-k3s/server-ca.crt").Handler(fileHandler(serverConfig.Runtime.ServerCA)) authed.Path(prefix + "/server-ca.crt").Handler(fileHandler(serverConfig.Runtime.ServerCA))
authed.Path("/v1-k3s/config").Handler(configHandler(serverConfig)) authed.Path(prefix + "/config").Handler(configHandler(serverConfig))
nodeAuthed := mux.NewRouter() nodeAuthed := mux.NewRouter()
nodeAuthed.Use(authMiddleware(serverConfig, "system:nodes")) nodeAuthed.Use(authMiddleware(serverConfig, "system:nodes"))
nodeAuthed.Path("/v1-k3s/connect").Handler(tunnel) nodeAuthed.Path(prefix + "/connect").Handler(tunnel)
nodeAuthed.NotFoundHandler = authed nodeAuthed.NotFoundHandler = authed
serverAuthed := mux.NewRouter() serverAuthed := mux.NewRouter()
serverAuthed.Use(authMiddleware(serverConfig, "k3s:server")) serverAuthed.Use(authMiddleware(serverConfig, version.Program+":server"))
serverAuthed.NotFoundHandler = nodeAuthed serverAuthed.NotFoundHandler = nodeAuthed
serverAuthed.Path("/db/info").Handler(nodeAuthed) serverAuthed.Path("/db/info").Handler(nodeAuthed)
if serverConfig.Runtime.HTTPBootstrap { if serverConfig.Runtime.HTTPBootstrap {
serverAuthed.Path("/v1-k3s/server-bootstrap").Handler(bootstrap.Handler(&serverConfig.Runtime.ControlRuntimeBootstrap)) serverAuthed.Path(prefix + "/server-bootstrap").Handler(bootstrap.Handler(&serverConfig.Runtime.ControlRuntimeBootstrap))
} }
staticDir := filepath.Join(serverConfig.DataDir, "static") staticDir := filepath.Join(serverConfig.DataDir, "static")
...@@ -68,17 +70,17 @@ func cacerts(ca []byte) http.Handler { ...@@ -68,17 +70,17 @@ func cacerts(ca []byte) http.Handler {
} }
func getNodeInfo(req *http.Request) (string, string, error) { func getNodeInfo(req *http.Request) (string, string, error) {
nodeNames := req.Header["K3s-Node-Name"] nodeName := req.Header.Get(version.Program + "-Node-Name")
if len(nodeNames) != 1 || nodeNames[0] == "" { if nodeName == "" {
return "", "", errors.New("node name not set") return "", "", errors.New("node name not set")
} }
nodePasswords := req.Header["K3s-Node-Password"] nodePassword := req.Header.Get(version.Program + "-Node-Password")
if len(nodePasswords) != 1 || nodePasswords[0] == "" { if nodePassword == "" {
return "", "", errors.New("node password not set") return "", "", errors.New("node password not set")
} }
return strings.ToLower(nodeNames[0]), nodePasswords[0], nil return strings.ToLower(nodeName), nodePassword, nil
} }
func getCACertAndKeys(caCertFile, caKeyFile, signingKeyFile string) ([]*x509.Certificate, crypto.Signer, crypto.Signer, error) { func getCACertAndKeys(caCertFile, caKeyFile, signingKeyFile string) ([]*x509.Certificate, crypto.Signer, crypto.Signer, error) {
......
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
"github.com/rancher/k3s/pkg/servicelb" "github.com/rancher/k3s/pkg/servicelb"
"github.com/rancher/k3s/pkg/static" "github.com/rancher/k3s/pkg/static"
"github.com/rancher/k3s/pkg/util" "github.com/rancher/k3s/pkg/util"
"github.com/rancher/k3s/pkg/version"
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1" v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
"github.com/rancher/wrangler/pkg/leader" "github.com/rancher/wrangler/pkg/leader"
"github.com/rancher/wrangler/pkg/resolvehome" "github.com/rancher/wrangler/pkg/resolvehome"
...@@ -114,12 +115,17 @@ func runControllers(ctx context.Context, config *Config) error { ...@@ -114,12 +115,17 @@ func runControllers(ctx context.Context, config *Config) error {
return err return err
} }
controlConfig.Runtime.Core = sc.Core
if config.ControlConfig.Runtime.ClusterControllerStart != nil {
if err := config.ControlConfig.Runtime.ClusterControllerStart(ctx); err != nil {
return errors.Wrapf(err, "starting cluster controllers")
}
}
if err := sc.Start(ctx); err != nil { if err := sc.Start(ctx); err != nil {
return err return err
} }
controlConfig.Runtime.Core = sc.Core
start := func(ctx context.Context) { start := func(ctx context.Context) {
if err := masterControllers(ctx, sc, config); err != nil { if err := masterControllers(ctx, sc, config); err != nil {
panic(err) panic(err)
...@@ -138,7 +144,7 @@ func runControllers(ctx context.Context, config *Config) error { ...@@ -138,7 +144,7 @@ func runControllers(ctx context.Context, config *Config) error {
logrus.Fatal("controllers exited") logrus.Fatal("controllers exited")
}() }()
} else { } else {
go leader.RunOrDie(ctx, "", "k3s", sc.K8s, start) go leader.RunOrDie(ctx, "", version.Program, sc.K8s, start)
} }
return nil return nil
...@@ -256,7 +262,7 @@ func writeKubeConfig(certs string, config *Config) error { ...@@ -256,7 +262,7 @@ func writeKubeConfig(certs string, config *Config) error {
kubeConfig, err := HomeKubeConfig(true, config.Rootless) kubeConfig, err := HomeKubeConfig(true, config.Rootless)
def := true def := true
if err != nil { if err != nil {
kubeConfig = filepath.Join(config.ControlConfig.DataDir, "kubeconfig-k3s.yaml") kubeConfig = filepath.Join(config.ControlConfig.DataDir, "kubeconfig-"+version.Program+".yaml")
def = false def = false
} }
kubeConfigSymlink := kubeConfig kubeConfigSymlink := kubeConfig
...@@ -333,7 +339,7 @@ func printToken(httpsPort int, advertiseIP, prefix, cmd string) { ...@@ -333,7 +339,7 @@ func printToken(httpsPort int, advertiseIP, prefix, cmd string) {
ip = hostIP.String() ip = hostIP.String()
} }
logrus.Infof("%s k3s %s -s https://%s:%d -t ${NODE_TOKEN}", prefix, cmd, ip, httpsPort) logrus.Infof("%s %s %s -s https://%s:%d -t ${NODE_TOKEN}", prefix, version.Program, cmd, ip, httpsPort)
} }
func FormatToken(token string, certFile string) (string, error) { func FormatToken(token string, certFile string) (string, error) {
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"github.com/rancher/k3s/pkg/version"
appclient "github.com/rancher/wrangler-api/pkg/generated/controllers/apps/v1" appclient "github.com/rancher/wrangler-api/pkg/generated/controllers/apps/v1"
coreclient "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1" coreclient "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
"github.com/rancher/wrangler/pkg/apply" "github.com/rancher/wrangler/pkg/apply"
...@@ -26,12 +27,15 @@ import ( ...@@ -26,12 +27,15 @@ import (
coregetter "k8s.io/client-go/kubernetes/typed/core/v1" coregetter "k8s.io/client-go/kubernetes/typed/core/v1"
) )
var (
svcNameLabel = "svccontroller." + version.Program + ".cattle.io/svcname"
daemonsetNodeLabel = "svccontroller." + version.Program + ".cattle.io/enablelb"
nodeSelectorLabel = "svccontroller." + version.Program + ".cattle.io/nodeselector"
)
const ( const (
image = "rancher/klipper-lb:v0.1.2" image = "rancher/klipper-lb:v0.1.2"
svcNameLabel = "svccontroller.k3s.cattle.io/svcname" Ready = condition.Cond("Ready")
daemonsetNodeLabel = "svccontroller.k3s.cattle.io/enablelb"
nodeSelectorLabel = "svccontroller.k3s.cattle.io/nodeselector"
Ready = condition.Cond("Ready")
) )
var ( var (
......
package version package version
import "strings"
var ( var (
Version = "dev" Program = "k3s"
GitCommit = "HEAD" ProgramUpper = strings.ToUpper("k3s")
Version = "dev"
GitCommit = "HEAD"
) )
...@@ -39,15 +39,10 @@ STATIC=" ...@@ -39,15 +39,10 @@ STATIC="
-extldflags '-static' -extldflags '-static'
" "
if [ "$DQLITE" = "true" ]; then
DQLITE_TAGS="dqlite"
DQLITE_STATIC_SQLITE="-luv -lraft -lco"
fi
STATIC_SQLITE=" STATIC_SQLITE="
-extldflags '-static -lm -ldl -lz -lpthread $DQLITE_STATIC_SQLITE' -extldflags '-static -lm -ldl -lz -lpthread'
" "
TAGS="ctrd apparmor seccomp no_btrfs netcgo osusergo providerless $DQLITE_TAGS" TAGS="ctrd apparmor seccomp no_btrfs netcgo osusergo providerless"
RUNC_TAGS="apparmor seccomp" RUNC_TAGS="apparmor seccomp"
RUNC_STATIC="static" RUNC_STATIC="static"
......
...@@ -56,9 +56,6 @@ LDFLAGS=" ...@@ -56,9 +56,6 @@ LDFLAGS="
-w -s -w -s
" "
STATIC="-extldflags '-static'" STATIC="-extldflags '-static'"
if [ "$DQLITE" = 'true' ]; then CGO_ENABLED=0 "${GO}" build -ldflags "$LDFLAGS $STATIC" -o ${CMD_NAME} ./cmd/k3s/main.go
DQLITE_TAGS='dqlite'
fi
CGO_ENABLED=0 "${GO}" build -tags "$DQLITE_TAGS" -ldflags "$LDFLAGS $STATIC" -o ${CMD_NAME} ./cmd/k3s/main.go
./scripts/build-upload ${CMD_NAME} ${COMMIT} ./scripts/build-upload ${CMD_NAME} ${COMMIT}
...@@ -3,11 +3,9 @@ set -ve ...@@ -3,11 +3,9 @@ set -ve
apk add -q -f curl libc6-compat tzdata apk add -q -f curl libc6-compat tzdata
download_go download_go
download_dqlite
# --- # ---
cat <<EOF >/etc/profile.d/build.sh cat <<EOF >/etc/profile.d/build.sh
export SELINUX=true export SELINUX=true
export DQLITE=true
export STATIC_BUILD=true export STATIC_BUILD=true
EOF EOF
. /etc/profile.d/build.sh . /etc/profile.d/build.sh
...@@ -28,4 +26,4 @@ EOF ...@@ -28,4 +26,4 @@ EOF
else else
echo "Using host docker server v$(cat /tmp/docker-server-version)" echo "Using host docker server v$(cat /tmp/docker-server-version)"
fi fi
) )
\ No newline at end of file
...@@ -5,7 +5,6 @@ download_go ...@@ -5,7 +5,6 @@ download_go
# --- # ---
cat <<EOF >/etc/profile.d/build.sh cat <<EOF >/etc/profile.d/build.sh
export SELINUX=true export SELINUX=true
# export DQLITE=true
# export STATIC_BUILD=true # export STATIC_BUILD=true
EOF EOF
. /etc/profile.d/build.sh . /etc/profile.d/build.sh
......
...@@ -5,7 +5,6 @@ download_go ...@@ -5,7 +5,6 @@ download_go
# --- # ---
cat <<EOF >/etc/profile.d/build.sh cat <<EOF >/etc/profile.d/build.sh
export SELINUX=true export SELINUX=true
# export DQLITE=true
# export STATIC_BUILD=true # export STATIC_BUILD=true
EOF EOF
. /etc/profile.d/build.sh . /etc/profile.d/build.sh
......
...@@ -79,18 +79,6 @@ download_go() { ...@@ -79,18 +79,6 @@ download_go() {
curl -sL https://storage.googleapis.com/golang/go${goversion}.linux-${ARCH}.tar.gz | tar -xzf - -C /usr/local curl -sL https://storage.googleapis.com/golang/go${goversion}.linux-${ARCH}.tar.gz | tar -xzf - -C /usr/local
} }
# --- Utility function to download dqlite
download_dqlite() {
dqliteURL="https://github.com/$(grep dqlite-build Dockerfile.dapper | sed -e 's/^.*--from=\([^ ]*\).*$/\1/' -e 's|:|/releases/download/|')/dqlite-$ARCH.tgz"
if [ -z "$dqliteURL" ]; then
echo 'Cannot find dqlite URL to fetch'
return 1
fi
mkdir -p /usr/src/
echo "Downloading DQLITE from $dqliteURL"
curl -sL $dqliteURL -o /usr/src/dqlite.tgz
}
# --- Run vagrant provision script if available # --- Run vagrant provision script if available
if [ ! -f "${PROVISION}" ]; then if [ ! -f "${PROVISION}" ]; then
echo "WARNING: Unable to execute provision script \"${PROVISION}\"" echo "WARNING: Unable to execute provision script \"${PROVISION}\""
......
...@@ -27,4 +27,3 @@ E2E_OUTPUT=$artifacts test-run-sonobuoy ...@@ -27,4 +27,3 @@ E2E_OUTPUT=$artifacts test-run-sonobuoy
test-run-sonobuoy mysql test-run-sonobuoy mysql
test-run-sonobuoy postgres test-run-sonobuoy postgres
# test-run-sonobuoy dqlite
\ No newline at end of file
CoreOS Project
Copyright 2018 CoreOS, Inc
This product includes software developed at CoreOS, Inc.
(http://www.coreos.com/).
// Copyright 2013-2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Semantic Versions http://semver.org
package semver
import (
"bytes"
"errors"
"fmt"
"regexp"
"strconv"
"strings"
)
type Version struct {
Major int64
Minor int64
Patch int64
PreRelease PreRelease
Metadata string
}
type PreRelease string
func splitOff(input *string, delim string) (val string) {
parts := strings.SplitN(*input, delim, 2)
if len(parts) == 2 {
*input = parts[0]
val = parts[1]
}
return val
}
func New(version string) *Version {
return Must(NewVersion(version))
}
func NewVersion(version string) (*Version, error) {
v := Version{}
if err := v.Set(version); err != nil {
return nil, err
}
return &v, nil
}
// Must is a helper for wrapping NewVersion and will panic if err is not nil.
func Must(v *Version, err error) *Version {
if err != nil {
panic(err)
}
return v
}
// Set parses and updates v from the given version string. Implements flag.Value
func (v *Version) Set(version string) error {
metadata := splitOff(&version, "+")
preRelease := PreRelease(splitOff(&version, "-"))
dotParts := strings.SplitN(version, ".", 3)
if len(dotParts) != 3 {
return fmt.Errorf("%s is not in dotted-tri format", version)
}
if err := validateIdentifier(string(preRelease)); err != nil {
return fmt.Errorf("failed to validate pre-release: %v", err)
}
if err := validateIdentifier(metadata); err != nil {
return fmt.Errorf("failed to validate metadata: %v", err)
}
parsed := make([]int64, 3, 3)
for i, v := range dotParts[:3] {
val, err := strconv.ParseInt(v, 10, 64)
parsed[i] = val
if err != nil {
return err
}
}
v.Metadata = metadata
v.PreRelease = preRelease
v.Major = parsed[0]
v.Minor = parsed[1]
v.Patch = parsed[2]
return nil
}
func (v Version) String() string {
var buffer bytes.Buffer
fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch)
if v.PreRelease != "" {
fmt.Fprintf(&buffer, "-%s", v.PreRelease)
}
if v.Metadata != "" {
fmt.Fprintf(&buffer, "+%s", v.Metadata)
}
return buffer.String()
}
func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error {
var data string
if err := unmarshal(&data); err != nil {
return err
}
return v.Set(data)
}
func (v Version) MarshalJSON() ([]byte, error) {
return []byte(`"` + v.String() + `"`), nil
}
func (v *Version) UnmarshalJSON(data []byte) error {
l := len(data)
if l == 0 || string(data) == `""` {
return nil
}
if l < 2 || data[0] != '"' || data[l-1] != '"' {
return errors.New("invalid semver string")
}
return v.Set(string(data[1 : l-1]))
}
// Compare tests if v is less than, equal to, or greater than versionB,
// returning -1, 0, or +1 respectively.
func (v Version) Compare(versionB Version) int {
if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 {
return cmp
}
return preReleaseCompare(v, versionB)
}
// Equal tests if v is equal to versionB.
func (v Version) Equal(versionB Version) bool {
return v.Compare(versionB) == 0
}
// LessThan tests if v is less than versionB.
func (v Version) LessThan(versionB Version) bool {
return v.Compare(versionB) < 0
}
// Slice converts the comparable parts of the semver into a slice of integers.
func (v Version) Slice() []int64 {
return []int64{v.Major, v.Minor, v.Patch}
}
func (p PreRelease) Slice() []string {
preRelease := string(p)
return strings.Split(preRelease, ".")
}
func preReleaseCompare(versionA Version, versionB Version) int {
a := versionA.PreRelease
b := versionB.PreRelease
/* Handle the case where if two versions are otherwise equal it is the
* one without a PreRelease that is greater */
if len(a) == 0 && (len(b) > 0) {
return 1
} else if len(b) == 0 && (len(a) > 0) {
return -1
}
// If there is a prerelease, check and compare each part.
return recursivePreReleaseCompare(a.Slice(), b.Slice())
}
func recursiveCompare(versionA []int64, versionB []int64) int {
if len(versionA) == 0 {
return 0
}
a := versionA[0]
b := versionB[0]
if a > b {
return 1
} else if a < b {
return -1
}
return recursiveCompare(versionA[1:], versionB[1:])
}
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
// A larger set of pre-release fields has a higher precedence than a smaller set,
// if all of the preceding identifiers are equal.
if len(versionA) == 0 {
if len(versionB) > 0 {
return -1
}
return 0
} else if len(versionB) == 0 {
// We're longer than versionB so return 1.
return 1
}
a := versionA[0]
b := versionB[0]
aInt := false
bInt := false
aI, err := strconv.Atoi(versionA[0])
if err == nil {
aInt = true
}
bI, err := strconv.Atoi(versionB[0])
if err == nil {
bInt = true
}
// Numeric identifiers always have lower precedence than non-numeric identifiers.
if aInt && !bInt {
return -1
} else if !aInt && bInt {
return 1
}
// Handle Integer Comparison
if aInt && bInt {
if aI > bI {
return 1
} else if aI < bI {
return -1
}
}
// Handle String Comparison
if a > b {
return 1
} else if a < b {
return -1
}
return recursivePreReleaseCompare(versionA[1:], versionB[1:])
}
// BumpMajor increments the Major field by 1 and resets all other fields to their default values
func (v *Version) BumpMajor() {
v.Major += 1
v.Minor = 0
v.Patch = 0
v.PreRelease = PreRelease("")
v.Metadata = ""
}
// BumpMinor increments the Minor field by 1 and resets all other fields to their default values
func (v *Version) BumpMinor() {
v.Minor += 1
v.Patch = 0
v.PreRelease = PreRelease("")
v.Metadata = ""
}
// BumpPatch increments the Patch field by 1 and resets all other fields to their default values
func (v *Version) BumpPatch() {
v.Patch += 1
v.PreRelease = PreRelease("")
v.Metadata = ""
}
// validateIdentifier makes sure the provided identifier satisfies semver spec
func validateIdentifier(id string) error {
if id != "" && !reIdentifier.MatchString(id) {
return fmt.Errorf("%s is not a valid semver identifier", id)
}
return nil
}
// reIdentifier is a regular expression used to check that pre-release and metadata
// identifiers satisfy the spec requirements
var reIdentifier = regexp.MustCompile(`^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$`)
// Copyright 2013-2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package semver
import (
"sort"
)
type Versions []*Version
func (s Versions) Len() int {
return len(s)
}
func (s Versions) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s Versions) Less(i, j int) bool {
return s[i].LessThan(*s[j])
}
// Sort sorts the given slice of Version
func Sort(versions []*Version) {
sort.Sort(Versions(versions))
}
sudo: false
language: go
go:
- 1.3.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- master
matrix:
allow_failures:
- go: master
fast_finish: true
install:
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d -s .)
- go tool vet .
- go test -v -race ./...
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
<http://www.opensource.org/licenses/mit-license.php>
# Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize)
Just a few functions for helping humanize times and sizes.
`go get` it as `github.com/dustin/go-humanize`, import it as
`"github.com/dustin/go-humanize"`, use it as `humanize`.
See [godoc](https://godoc.org/github.com/dustin/go-humanize) for
complete documentation.
## Sizes
This lets you take numbers like `82854982` and convert them to useful
strings like, `83 MB` or `79 MiB` (whichever you prefer).
Example:
```go
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
```
## Times
This lets you take a `time.Time` and spit it out in relative terms.
For example, `12 seconds ago` or `3 days from now`.
Example:
```go
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
```
Thanks to Kyle Lemons for the time implementation from an IRC
conversation one day. It's pretty neat.
## Ordinals
From a [mailing list discussion][odisc] where a user wanted to be able
to label ordinals.
0 -> 0th
1 -> 1st
2 -> 2nd
3 -> 3rd
4 -> 4th
[...]
Example:
```go
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
```
## Commas
Want to shove commas into numbers? Be my guest.
0 -> 0
100 -> 100
1000 -> 1,000
1000000000 -> 1,000,000,000
-100000 -> -100,000
Example:
```go
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
```
## Ftoa
Nicer float64 formatter that removes trailing zeros.
```go
fmt.Printf("%f", 2.24) // 2.240000
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
fmt.Printf("%f", 2.0) // 2.000000
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
```
## SI notation
Format numbers with [SI notation][sinotation].
Example:
```go
humanize.SI(0.00000000223, "M") // 2.23 nM
```
## English-specific functions
The following functions are in the `humanize/english` subpackage.
### Plurals
Simple English pluralization
```go
english.PluralWord(1, "object", "") // object
english.PluralWord(42, "object", "") // objects
english.PluralWord(2, "bus", "") // buses
english.PluralWord(99, "locus", "loci") // loci
english.Plural(1, "object", "") // 1 object
english.Plural(42, "object", "") // 42 objects
english.Plural(2, "bus", "") // 2 buses
english.Plural(99, "locus", "loci") // 99 loci
```
### Word series
Format comma-separated words lists with conjuctions:
```go
english.WordSeries([]string{"foo"}, "and") // foo
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
```
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
package humanize
import (
"math/big"
)
// order of magnitude (to a max order)
func oomm(n, b *big.Int, maxmag int) (float64, int) {
mag := 0
m := &big.Int{}
for n.Cmp(b) >= 0 {
n.DivMod(n, b, m)
mag++
if mag == maxmag && maxmag >= 0 {
break
}
}
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
}
// total order of magnitude
// (same as above, but with no upper limit)
func oom(n, b *big.Int) (float64, int) {
mag := 0
m := &big.Int{}
for n.Cmp(b) >= 0 {
n.DivMod(n, b, m)
mag++
}
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
}
package humanize
import (
"fmt"
"math/big"
"strings"
"unicode"
)
var (
bigIECExp = big.NewInt(1024)
// BigByte is one byte in bit.Ints
BigByte = big.NewInt(1)
// BigKiByte is 1,024 bytes in bit.Ints
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
// BigMiByte is 1,024 k bytes in bit.Ints
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
// BigGiByte is 1,024 m bytes in bit.Ints
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
// BigTiByte is 1,024 g bytes in bit.Ints
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
// BigPiByte is 1,024 t bytes in bit.Ints
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
// BigEiByte is 1,024 p bytes in bit.Ints
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
// BigZiByte is 1,024 e bytes in bit.Ints
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
// BigYiByte is 1,024 z bytes in bit.Ints
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
)
var (
bigSIExp = big.NewInt(1000)
// BigSIByte is one SI byte in big.Ints
BigSIByte = big.NewInt(1)
// BigKByte is 1,000 SI bytes in big.Ints
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
// BigMByte is 1,000 SI k bytes in big.Ints
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
// BigGByte is 1,000 SI m bytes in big.Ints
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
// BigTByte is 1,000 SI g bytes in big.Ints
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
// BigPByte is 1,000 SI t bytes in big.Ints
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
// BigEByte is 1,000 SI p bytes in big.Ints
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
// BigZByte is 1,000 SI e bytes in big.Ints
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
// BigYByte is 1,000 SI z bytes in big.Ints
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
)
var bigBytesSizeTable = map[string]*big.Int{
"b": BigByte,
"kib": BigKiByte,
"kb": BigKByte,
"mib": BigMiByte,
"mb": BigMByte,
"gib": BigGiByte,
"gb": BigGByte,
"tib": BigTiByte,
"tb": BigTByte,
"pib": BigPiByte,
"pb": BigPByte,
"eib": BigEiByte,
"eb": BigEByte,
"zib": BigZiByte,
"zb": BigZByte,
"yib": BigYiByte,
"yb": BigYByte,
// Without suffix
"": BigByte,
"ki": BigKiByte,
"k": BigKByte,
"mi": BigMiByte,
"m": BigMByte,
"gi": BigGiByte,
"g": BigGByte,
"ti": BigTiByte,
"t": BigTByte,
"pi": BigPiByte,
"p": BigPByte,
"ei": BigEiByte,
"e": BigEByte,
"z": BigZByte,
"zi": BigZiByte,
"y": BigYByte,
"yi": BigYiByte,
}
var ten = big.NewInt(10)
func humanateBigBytes(s, base *big.Int, sizes []string) string {
if s.Cmp(ten) < 0 {
return fmt.Sprintf("%d B", s)
}
c := (&big.Int{}).Set(s)
val, mag := oomm(c, base, len(sizes)-1)
suffix := sizes[mag]
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
// BigBytes produces a human readable representation of an SI size.
//
// See also: ParseBigBytes.
//
// BigBytes(82854982) -> 83 MB
func BigBytes(s *big.Int) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
return humanateBigBytes(s, bigSIExp, sizes)
}
// BigIBytes produces a human readable representation of an IEC size.
//
// See also: ParseBigBytes.
//
// BigIBytes(82854982) -> 79 MiB
func BigIBytes(s *big.Int) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
return humanateBigBytes(s, bigIECExp, sizes)
}
// ParseBigBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// See also: BigBytes, BigIBytes.
//
// ParseBigBytes("42 MB") -> 42000000, nil
// ParseBigBytes("42 mib") -> 44040192, nil
func ParseBigBytes(s string) (*big.Int, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
val := &big.Rat{}
_, err := fmt.Sscanf(num, "%f", val)
if err != nil {
return nil, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := bigBytesSizeTable[extra]; ok {
mv := (&big.Rat{}).SetInt(m)
val.Mul(val, mv)
rv := &big.Int{}
rv.Div(val.Num(), val.Denom())
return rv, nil
}
return nil, fmt.Errorf("unhandled size name: %v", extra)
}
package humanize
import (
"fmt"
"math"
"strconv"
"strings"
"unicode"
)
// IEC Sizes.
// kibis of bits
const (
Byte = 1 << (iota * 10)
KiByte
MiByte
GiByte
TiByte
PiByte
EiByte
)
// SI Sizes.
const (
IByte = 1
KByte = IByte * 1000
MByte = KByte * 1000
GByte = MByte * 1000
TByte = GByte * 1000
PByte = TByte * 1000
EByte = PByte * 1000
)
var bytesSizeTable = map[string]uint64{
"b": Byte,
"kib": KiByte,
"kb": KByte,
"mib": MiByte,
"mb": MByte,
"gib": GiByte,
"gb": GByte,
"tib": TiByte,
"tb": TByte,
"pib": PiByte,
"pb": PByte,
"eib": EiByte,
"eb": EByte,
// Without suffix
"": Byte,
"ki": KiByte,
"k": KByte,
"mi": MiByte,
"m": MByte,
"gi": GiByte,
"g": GByte,
"ti": TiByte,
"t": TByte,
"pi": PiByte,
"p": PByte,
"ei": EiByte,
"e": EByte,
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
f := "%.0f %s"
if val < 10 {
f = "%.1f %s"
}
return fmt.Sprintf(f, val, suffix)
}
// Bytes produces a human readable representation of an SI size.
//
// See also: ParseBytes.
//
// Bytes(82854982) -> 83 MB
func Bytes(s uint64) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
return humanateBytes(s, 1000, sizes)
}
// IBytes produces a human readable representation of an IEC size.
//
// See also: ParseBytes.
//
// IBytes(82854982) -> 79 MiB
func IBytes(s uint64) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
return humanateBytes(s, 1024, sizes)
}
// ParseBytes parses a string representation of bytes into the number
// of bytes it represents.
//
// See Also: Bytes, IBytes.
//
// ParseBytes("42 MB") -> 42000000, nil
// ParseBytes("42 mib") -> 44040192, nil
func ParseBytes(s string) (uint64, error) {
lastDigit := 0
hasComma := false
for _, r := range s {
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
break
}
if r == ',' {
hasComma = true
}
lastDigit++
}
num := s[:lastDigit]
if hasComma {
num = strings.Replace(num, ",", "", -1)
}
f, err := strconv.ParseFloat(num, 64)
if err != nil {
return 0, err
}
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
if m, ok := bytesSizeTable[extra]; ok {
f *= float64(m)
if f >= math.MaxUint64 {
return 0, fmt.Errorf("too large: %v", s)
}
return uint64(f), nil
}
return 0, fmt.Errorf("unhandled size name: %v", extra)
}
package humanize
import (
"bytes"
"math"
"math/big"
"strconv"
"strings"
)
// Comma produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Comma(834142) -> 834,142
func Comma(v int64) string {
sign := ""
// Min int64 can't be negated to a usable value, so it has to be special cased.
if v == math.MinInt64 {
return "-9,223,372,036,854,775,808"
}
if v < 0 {
sign = "-"
v = 0 - v
}
parts := []string{"", "", "", "", "", "", ""}
j := len(parts) - 1
for v > 999 {
parts[j] = strconv.FormatInt(v%1000, 10)
switch len(parts[j]) {
case 2:
parts[j] = "0" + parts[j]
case 1:
parts[j] = "00" + parts[j]
}
v = v / 1000
j--
}
parts[j] = strconv.Itoa(int(v))
return sign + strings.Join(parts[j:], ",")
}
// Commaf produces a string form of the given number in base 10 with
// commas after every three orders of magnitude.
//
// e.g. Commaf(834142.32) -> 834,142.32
func Commaf(v float64) string {
buf := &bytes.Buffer{}
if v < 0 {
buf.Write([]byte{'-'})
v = 0 - v
}
comma := []byte{','}
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return buf.String()
}
// CommafWithDigits works like the Commaf but limits the resulting
// string to the given number of decimal places.
//
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
func CommafWithDigits(f float64, decimals int) string {
return stripTrailingDigits(Commaf(f), decimals)
}
// BigComma produces a string form of the given big.Int in base 10
// with commas after every three orders of magnitude.
func BigComma(b *big.Int) string {
sign := ""
if b.Sign() < 0 {
sign = "-"
b.Abs(b)
}
athousand := big.NewInt(1000)
c := (&big.Int{}).Set(b)
_, m := oom(c, athousand)
parts := make([]string, m+1)
j := len(parts) - 1
mod := &big.Int{}
for b.Cmp(athousand) >= 0 {
b.DivMod(b, athousand, mod)
parts[j] = strconv.FormatInt(mod.Int64(), 10)
switch len(parts[j]) {
case 2:
parts[j] = "0" + parts[j]
case 1:
parts[j] = "00" + parts[j]
}
j--
}
parts[j] = strconv.Itoa(int(b.Int64()))
return sign + strings.Join(parts[j:], ",")
}
// +build go1.6
package humanize
import (
"bytes"
"math/big"
"strings"
)
// BigCommaf produces a string form of the given big.Float in base 10
// with commas after every three orders of magnitude.
func BigCommaf(v *big.Float) string {
buf := &bytes.Buffer{}
if v.Sign() < 0 {
buf.Write([]byte{'-'})
v.Abs(v)
}
comma := []byte{','}
parts := strings.Split(v.Text('f', -1), ".")
pos := 0
if len(parts[0])%3 != 0 {
pos += len(parts[0]) % 3
buf.WriteString(parts[0][:pos])
buf.Write(comma)
}
for ; pos < len(parts[0]); pos += 3 {
buf.WriteString(parts[0][pos : pos+3])
buf.Write(comma)
}
buf.Truncate(buf.Len() - 1)
if len(parts) > 1 {
buf.Write([]byte{'.'})
buf.WriteString(parts[1])
}
return buf.String()
}
package humanize
import (
"strconv"
"strings"
)
func stripTrailingZeros(s string) string {
offset := len(s) - 1
for offset > 0 {
if s[offset] == '.' {
offset--
break
}
if s[offset] != '0' {
break
}
offset--
}
return s[:offset+1]
}
func stripTrailingDigits(s string, digits int) string {
if i := strings.Index(s, "."); i >= 0 {
if digits <= 0 {
return s[:i]
}
i++
if i+digits >= len(s) {
return s
}
return s[:i+digits]
}
return s
}
// Ftoa converts a float to a string with no trailing zeros.
func Ftoa(num float64) string {
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
}
// FtoaWithDigits converts a float to a string but limits the resulting string
// to the given number of decimal places, and no trailing zeros.
func FtoaWithDigits(num float64, digits int) string {
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
}
/*
Package humanize converts boring ugly numbers to human-friendly strings and back.
Durations can be turned into strings such as "3 days ago", numbers
representing sizes like 82854982 into useful strings like, "83 MB" or
"79 MiB" (whichever you prefer).
*/
package humanize
package humanize
/*
Slightly adapted from the source to fit go-humanize.
Author: https://github.com/gorhill
Source: https://gist.github.com/gorhill/5285193
*/
import (
"math"
"strconv"
)
var (
renderFloatPrecisionMultipliers = [...]float64{
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
}
renderFloatPrecisionRounders = [...]float64{
0.5,
0.05,
0.005,
0.0005,
0.00005,
0.000005,
0.0000005,
0.00000005,
0.000000005,
0.0000000005,
}
)
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
// * thousands separator
// * decimal separator
// * decimal precision
//
// Usage: s := RenderFloat(format, n)
// The format parameter tells how to render the number n.
//
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
//
// Examples of format strings, given n = 12345.6789:
// "#,###.##" => "12,345.67"
// "#,###." => "12,345"
// "#,###" => "12345,678"
// "#\u202F###,##" => "12 345,68"
// "#.###,###### => 12.345,678900
// "" (aka default format) => 12,345.67
//
// The highest precision allowed is 9 digits after the decimal symbol.
// There is also a version for integer number, FormatInteger(),
// which is convenient for calls within template.
func FormatFloat(format string, n float64) string {
// Special cases:
// NaN = "NaN"
// +Inf = "+Infinity"
// -Inf = "-Infinity"
if math.IsNaN(n) {
return "NaN"
}
if n > math.MaxFloat64 {
return "Infinity"
}
if n < -math.MaxFloat64 {
return "-Infinity"
}
// default format
precision := 2
decimalStr := "."
thousandStr := ","
positiveStr := ""
negativeStr := "-"
if len(format) > 0 {
format := []rune(format)
// If there is an explicit format directive,
// then default values are these:
precision = 9
thousandStr = ""
// collect indices of meaningful formatting directives
formatIndx := []int{}
for i, char := range format {
if char != '#' && char != '0' {
formatIndx = append(formatIndx, i)
}
}
if len(formatIndx) > 0 {
// Directive at index 0:
// Must be a '+'
// Raise an error if not the case
// index: 0123456789
// +0.000,000
// +000,000.0
// +0000.00
// +0000
if formatIndx[0] == 0 {
if format[formatIndx[0]] != '+' {
panic("RenderFloat(): invalid positive sign directive")
}
positiveStr = "+"
formatIndx = formatIndx[1:]
}
// Two directives:
// First is thousands separator
// Raise an error if not followed by 3-digit
// 0123456789
// 0.000,000
// 000,000.00
if len(formatIndx) == 2 {
if (formatIndx[1] - formatIndx[0]) != 4 {
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
}
thousandStr = string(format[formatIndx[0]])
formatIndx = formatIndx[1:]
}
// One directive:
// Directive is decimal separator
// The number of digit-specifier following the separator indicates wanted precision
// 0123456789
// 0.00
// 000,0000
if len(formatIndx) == 1 {
decimalStr = string(format[formatIndx[0]])
precision = len(format) - formatIndx[0] - 1
}
}
}
// generate sign part
var signStr string
if n >= 0.000000001 {
signStr = positiveStr
} else if n <= -0.000000001 {
signStr = negativeStr
n = -n
} else {
signStr = ""
n = 0.0
}
// split number into integer and fractional parts
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
// generate integer part string
intStr := strconv.FormatInt(int64(intf), 10)
// add thousand separator if required
if len(thousandStr) > 0 {
for i := len(intStr); i > 3; {
i -= 3
intStr = intStr[:i] + thousandStr + intStr[i:]
}
}
// no fractional part, we can leave now
if precision == 0 {
return signStr + intStr
}
// generate fractional part
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
// may need padding
if len(fracStr) < precision {
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
}
return signStr + intStr + decimalStr + fracStr
}
// FormatInteger produces a formatted number as string.
// See FormatFloat.
func FormatInteger(format string, n int) string {
return FormatFloat(format, float64(n))
}
package humanize
import "strconv"
// Ordinal gives you the input number in a rank/ordinal format.
//
// Ordinal(3) -> 3rd
func Ordinal(x int) string {
suffix := "th"
switch x % 10 {
case 1:
if x%100 != 11 {
suffix = "st"
}
case 2:
if x%100 != 12 {
suffix = "nd"
}
case 3:
if x%100 != 13 {
suffix = "rd"
}
}
return strconv.Itoa(x) + suffix
}
package humanize
import (
"errors"
"math"
"regexp"
"strconv"
)
var siPrefixTable = map[float64]string{
-24: "y", // yocto
-21: "z", // zepto
-18: "a", // atto
-15: "f", // femto
-12: "p", // pico
-9: "n", // nano
-6: "µ", // micro
-3: "m", // milli
0: "",
3: "k", // kilo
6: "M", // mega
9: "G", // giga
12: "T", // tera
15: "P", // peta
18: "E", // exa
21: "Z", // zetta
24: "Y", // yotta
}
var revSIPrefixTable = revfmap(siPrefixTable)
// revfmap reverses the map and precomputes the power multiplier
func revfmap(in map[float64]string) map[string]float64 {
rv := map[string]float64{}
for k, v := range in {
rv[v] = math.Pow(10, k)
}
return rv
}
var riParseRegex *regexp.Regexp
func init() {
ri := `^([\-0-9.]+)\s?([`
for _, v := range siPrefixTable {
ri += v
}
ri += `]?)(.*)`
riParseRegex = regexp.MustCompile(ri)
}
// ComputeSI finds the most appropriate SI prefix for the given number
// and returns the prefix along with the value adjusted to be within
// that prefix.
//
// See also: SI, ParseSI.
//
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
func ComputeSI(input float64) (float64, string) {
if input == 0 {
return 0, ""
}
mag := math.Abs(input)
exponent := math.Floor(logn(mag, 10))
exponent = math.Floor(exponent/3) * 3
value := mag / math.Pow(10, exponent)
// Handle special case where value is exactly 1000.0
// Should return 1 M instead of 1000 k
if value == 1000.0 {
exponent += 3
value = mag / math.Pow(10, exponent)
}
value = math.Copysign(value, input)
prefix := siPrefixTable[exponent]
return value, prefix
}
// SI returns a string with default formatting.
//
// SI uses Ftoa to format float value, removing trailing zeros.
//
// See also: ComputeSI, ParseSI.
//
// e.g. SI(1000000, "B") -> 1 MB
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
func SI(input float64, unit string) string {
value, prefix := ComputeSI(input)
return Ftoa(value) + " " + prefix + unit
}
// SIWithDigits works like SI but limits the resulting string to the
// given number of decimal places.
//
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
func SIWithDigits(input float64, decimals int, unit string) string {
value, prefix := ComputeSI(input)
return FtoaWithDigits(value, decimals) + " " + prefix + unit
}
var errInvalid = errors.New("invalid input")
// ParseSI parses an SI string back into the number and unit.
//
// See also: SI, ComputeSI.
//
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
func ParseSI(input string) (float64, string, error) {
found := riParseRegex.FindStringSubmatch(input)
if len(found) != 4 {
return 0, "", errInvalid
}
mag := revSIPrefixTable[found[2]]
unit := found[3]
base, err := strconv.ParseFloat(found[1], 64)
return base * mag, unit, err
}
package humanize
import (
"fmt"
"math"
"sort"
"time"
)
// Seconds-based time units
const (
Day = 24 * time.Hour
Week = 7 * Day
Month = 30 * Day
Year = 12 * Month
LongTime = 37 * Year
)
// Time formats a time into a relative string.
//
// Time(someT) -> "3 weeks ago"
func Time(then time.Time) string {
return RelTime(then, time.Now(), "ago", "from now")
}
// A RelTimeMagnitude struct contains a relative time point at which
// the relative format of time will switch to a new format string. A
// slice of these in ascending order by their "D" field is passed to
// CustomRelTime to format durations.
//
// The Format field is a string that may contain a "%s" which will be
// replaced with the appropriate signed label (e.g. "ago" or "from
// now") and a "%d" that will be replaced by the quantity.
//
// The DivBy field is the amount of time the time difference must be
// divided by in order to display correctly.
//
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
// DivBy should be time.Minute so whatever the duration is will be
// expressed in minutes.
type RelTimeMagnitude struct {
D time.Duration
Format string
DivBy time.Duration
}
var defaultMagnitudes = []RelTimeMagnitude{
{time.Second, "now", time.Second},
{2 * time.Second, "1 second %s", 1},
{time.Minute, "%d seconds %s", time.Second},
{2 * time.Minute, "1 minute %s", 1},
{time.Hour, "%d minutes %s", time.Minute},
{2 * time.Hour, "1 hour %s", 1},
{Day, "%d hours %s", time.Hour},
{2 * Day, "1 day %s", 1},
{Week, "%d days %s", Day},
{2 * Week, "1 week %s", 1},
{Month, "%d weeks %s", Week},
{2 * Month, "1 month %s", 1},
{Year, "%d months %s", Month},
{18 * Month, "1 year %s", 1},
{2 * Year, "2 years %s", 1},
{LongTime, "%d years %s", Year},
{math.MaxInt64, "a long while %s", 1},
}
// RelTime formats a time into a relative string.
//
// It takes two times and two labels. In addition to the generic time
// delta string (e.g. 5 minutes), the labels are used applied so that
// the label corresponding to the smaller time is applied.
//
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
func RelTime(a, b time.Time, albl, blbl string) string {
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
}
// CustomRelTime formats a time into a relative string.
//
// It takes two times two labels and a table of relative time formats.
// In addition to the generic time delta string (e.g. 5 minutes), the
// labels are used applied so that the label corresponding to the
// smaller time is applied.
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
lbl := albl
diff := b.Sub(a)
if a.After(b) {
lbl = blbl
diff = a.Sub(b)
}
n := sort.Search(len(magnitudes), func(i int) bool {
return magnitudes[i].D > diff
})
if n >= len(magnitudes) {
n = len(magnitudes) - 1
}
mag := magnitudes[n]
args := []interface{}{}
escaped := false
for _, ch := range mag.Format {
if escaped {
switch ch {
case 's':
args = append(args, lbl)
case 'd':
args = append(args, diff/mag.DivBy)
}
escaped = false
} else {
escaped = ch == '%'
}
}
return fmt.Sprintf(mag.Format, args...)
}
language: go
os:
- linux
- osx
go:
- 1.12
script:
- go test -v
Main author and maintainer of pongo2:
* Florian Schlachter <flori@n-schlachter.de>
Contributors (in no specific order):
* @romanoaugusto88
* @vitalbh
* @blaubaer
Feel free to add yourself to the list or to modify your entry if you did a contribution.
The MIT License (MIT)
Copyright (c) 2013-2014 Florian Schlachter
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package pongo2
import (
"regexp"
"github.com/juju/errors"
)
var reIdentifiers = regexp.MustCompile("^[a-zA-Z0-9_]+$")
var autoescape = true
func SetAutoescape(newValue bool) {
autoescape = newValue
}
// A Context type provides constants, variables, instances or functions to a template.
//
// pongo2 automatically provides meta-information or functions through the "pongo2"-key.
// Currently, context["pongo2"] contains the following keys:
// 1. version: returns the version string
//
// Template examples for accessing items from your context:
// {{ myconstant }}
// {{ myfunc("test", 42) }}
// {{ user.name }}
// {{ pongo2.version }}
type Context map[string]interface{}
func (c Context) checkForValidIdentifiers() *Error {
for k, v := range c {
if !reIdentifiers.MatchString(k) {
return &Error{
Sender: "checkForValidIdentifiers",
OrigError: errors.Errorf("context-key '%s' (value: '%+v') is not a valid identifier", k, v),
}
}
}
return nil
}
// Update updates this context with the key/value-pairs from another context.
func (c Context) Update(other Context) Context {
for k, v := range other {
c[k] = v
}
return c
}
// ExecutionContext contains all data important for the current rendering state.
//
// If you're writing a custom tag, your tag's Execute()-function will
// have access to the ExecutionContext. This struct stores anything
// about the current rendering process's Context including
// the Context provided by the user (field Public).
// You can safely use the Private context to provide data to the user's
// template (like a 'forloop'-information). The Shared-context is used
// to share data between tags. All ExecutionContexts share this context.
//
// Please be careful when accessing the Public data.
// PLEASE DO NOT MODIFY THE PUBLIC CONTEXT (read-only).
//
// To create your own execution context within tags, use the
// NewChildExecutionContext(parent) function.
type ExecutionContext struct {
template *Template
Autoescape bool
Public Context
Private Context
Shared Context
}
var pongo2MetaContext = Context{
"version": Version,
}
func newExecutionContext(tpl *Template, ctx Context) *ExecutionContext {
privateCtx := make(Context)
// Make the pongo2-related funcs/vars available to the context
privateCtx["pongo2"] = pongo2MetaContext
return &ExecutionContext{
template: tpl,
Public: ctx,
Private: privateCtx,
Autoescape: autoescape,
}
}
func NewChildExecutionContext(parent *ExecutionContext) *ExecutionContext {
newctx := &ExecutionContext{
template: parent.template,
Public: parent.Public,
Private: make(Context),
Autoescape: parent.Autoescape,
}
newctx.Shared = parent.Shared
// Copy all existing private items
newctx.Private.Update(parent.Private)
return newctx
}
func (ctx *ExecutionContext) Error(msg string, token *Token) *Error {
return ctx.OrigError(errors.New(msg), token)
}
func (ctx *ExecutionContext) OrigError(err error, token *Token) *Error {
filename := ctx.template.name
var line, col int
if token != nil {
// No tokens available
// TODO: Add location (from where?)
filename = token.Filename
line = token.Line
col = token.Col
}
return &Error{
Template: ctx.template,
Filename: filename,
Line: line,
Column: col,
Token: token,
Sender: "execution",
OrigError: err,
}
}
func (ctx *ExecutionContext) Logf(format string, args ...interface{}) {
ctx.template.set.logf(format, args...)
}
// A Django-syntax like template-engine
//
// Blog posts about pongo2 (including introduction and migration):
// https://www.florian-schlachter.de/?tag=pongo2
//
// Complete documentation on the template language:
// https://docs.djangoproject.com/en/dev/topics/templates/
//
// Try out pongo2 live in the pongo2 playground:
// https://www.florian-schlachter.de/pongo2/
//
// Make sure to read README.md in the repository as well.
//
// A tiny example with template strings:
//
// (Snippet on playground: https://www.florian-schlachter.de/pongo2/?id=1206546277)
//
// // Compile the template first (i. e. creating the AST)
// tpl, err := pongo2.FromString("Hello {{ name|capfirst }}!")
// if err != nil {
// panic(err)
// }
// // Now you can render the template with the given
// // pongo2.Context how often you want to.
// out, err := tpl.Execute(pongo2.Context{"name": "fred"})
// if err != nil {
// panic(err)
// }
// fmt.Println(out) // Output: Hello Fred!
//
package pongo2
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment