commit 9bb7c27c62c9a0a04ffa99cf9bc78a0985cea1a8 Author: Darren Shepherd Date: Tue Jan 1 01:23:01 2019 -0700 Initial Commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..e29bdc4e4a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +./bin +./.vagrant +./.dapper +./data-dir +./dist +./.trash-cache +./image/root +./image/agent +./image/go_build_agent +./image/main.squashfs diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000000..9ae8d11813 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,55 @@ +--- +pipeline: + build: + privileged: true + image: rancher/dapper:1.11.2 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + commands: + - dapper ci + + stage-binaries: + image: rancher/dapper:1.11.2 + commands: + - cp -f ./bin/rio-incluster ./package/rio + when: + branch: master + event: tag + + publish-image: + image: plugins/docker + dockerfile: package/Dockerfile + repo: rancher/rio + context: package/ + tag: ${DRONE_TAG} + secrets: [docker_username, docker_password] + when: + branch: master + event: tag + + github_binary_prerelease: + image: plugins/github-release + prerelease: true + files: + - dist/artifacts/* + checksum: + - sha256 + secrets: [github_token] + when: + branch: master + event: tag + ref: + include: [ refs/tags/*rc* ] + + github_binary_release: + image: plugins/github-release + files: + - dist/artifacts/* + checksum: + - sha256 + secrets: [github_token] + when: + branch: master + event: tag + ref: + exclude: [ refs/tags/*rc* ] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..2f485433e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +*.swp +/.dapper +/.idea +/.trash-cache +/.vagrant +/*.log +/bin +/build +/data-dir +/dist +/image/root +/image/agent +/image/go_build_agent +/image/main.squashfs +/package/rio +__pycache__ +/tests/.pytest_cache/ +/tests/.tox/ +/tests/.vscode + diff --git a/.gometalinter.json b/.gometalinter.json new file mode 100644 index 0000000000..3ae06d8d1a --- /dev/null +++ b/.gometalinter.json @@ -0,0 +1,11 @@ +{ + "EnableAll": false, + "Enable": [ + "golint", + "goimports", + "misspell", + "ineffassign", + "errcheck" + ], + "Deadline": "1m" +} \ No newline at end of file diff --git a/Dockerfile.dapper b/Dockerfile.dapper new file mode 100644 index 0000000000..4d92357872 --- /dev/null +++ b/Dockerfile.dapper @@ -0,0 +1,29 @@ +FROM golang:1.11-alpine3.8 + +RUN apk -U --no-cache add bash git gcc musl-dev docker vim less file curl wget ca-certificates jq linux-headers zlib-dev tar zip squashfs-tools npm coreutils \ + python3 py3-pip python3-dev openssl-dev libffi-dev +RUN pip3 install 'tox==3.6.0' +RUN npm install -g 'bats@1.1.0' +RUN apk -U --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/main/ add sqlite-dev sqlite-static +RUN go get -d golang.org/x/lint/golint && \ + git -C /go/src/golang.org/x/lint/golint checkout -b current 06c8688daad7faa9da5a0c2f163a3d14aac986ca && \ + go install golang.org/x/lint/golint && \ + rm -rf /go/src /go/pkg +RUN go get -d github.com/alecthomas/gometalinter && \ + git -C /go/src/github.com/alecthomas/gometalinter checkout -b current v2.0.11 && \ + go install github.com/alecthomas/gometalinter && \ + gometalinter --install && \ + rm -rf /go/src /go/pkg + +ENV DAPPER_RUN_ARGS --privileged +ENV DAPPER_ENV REPO TAG DRONE_TAG +ENV DAPPER_SOURCE /go/src/github.com/rancher/rio/ +ENV DAPPER_OUTPUT ./bin ./dist +ENV DAPPER_DOCKER_SOCKET true +ENV HOME ${DAPPER_SOURCE} +ENV CROSS true +WORKDIR ${DAPPER_SOURCE} + +ENTRYPOINT ["./scripts/entry"] +CMD ["ci"] + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..f433b1a53f --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..d7d72a16d5 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +TARGETS := $(shell ls scripts) + +.dapper: + @echo Downloading dapper + @curl -sL https://releases.rancher.com/dapper/latest/dapper-`uname -s`-`uname -m` > .dapper.tmp + @@chmod +x .dapper.tmp + @./.dapper.tmp -v + @mv .dapper.tmp .dapper + +$(TARGETS): .dapper + ./.dapper $@ + +trash: .dapper + ./.dapper -m bind trash + +trash-keep: .dapper + ./.dapper -m bind trash -k + +deps: trash + +.DEFAULT_GOAL := ci + +.PHONY: $(TARGETS) diff --git a/agent/config/config.go b/agent/config/config.go new file mode 100644 index 0000000000..0708daae8d --- /dev/null +++ b/agent/config/config.go @@ -0,0 +1,223 @@ +package config + +import ( + "crypto/md5" + "crypto/tls" + "encoding/hex" + "encoding/pem" + "fmt" + "io/ioutil" + "net/url" + "os" + "path/filepath" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/rancher/norman/pkg/clientaccess" + "github.com/rancher/rio/pkg/daemons/config" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/json" + net2 "k8s.io/apimachinery/pkg/util/net" + "k8s.io/client-go/util/cert" +) + +type envInfo struct { + ServerURL string + Token string + DataDir string + NodeIP string + NodeName string +} + +func Get() *config.Node { + for { + agentConfig, err := get() + if err != nil { + logrus.Error(err) + time.Sleep(5 * time.Second) + continue + } + return agentConfig + } +} + +func getEnvInfo() (*envInfo, error) { + u := os.Getenv("K3S_URL") + if u == "" { + return nil, fmt.Errorf("K3S_URL env var is required") + } + + _, err := url.Parse(u) + if err != nil { + return nil, fmt.Errorf("K3S_URL [%s] is invalid: %v", u, err) + } + + t := os.Getenv("K3S_TOKEN") + if t == "" { + return nil, fmt.Errorf("K3S_TOKEN env var is required") + } + + dataDir := os.Getenv("K3S_DATA_DIR") + if dataDir == "" { + return nil, fmt.Errorf("K3S_DATA_DIR is required") + } + + return &envInfo{ + ServerURL: u, + Token: t, + DataDir: dataDir, + NodeIP: os.Getenv("K3S_NODE_IP"), + NodeName: os.Getenv("NODE_NAME"), + }, nil +} + +func getNodeCert(info *clientaccess.Info) (*tls.Certificate, error) { + nodeCert, err := clientaccess.Get("/v1-k3s/node.cert", info) + if err != nil { + return nil, err + } + + nodeKey, err := clientaccess.Get("/v1-k3s/node.key", info) + if err != nil { + return nil, err + } + + cert, err := tls.X509KeyPair(nodeCert, nodeKey) + if err != nil { + return nil, err + } + return &cert, nil +} + +func writeNodeCA(dataDir string, nodeCert *tls.Certificate) (string, error) { + clientCABytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: nodeCert.Certificate[1], + }) + + clientCA := filepath.Join(dataDir, "client-ca.pem") + if err := ioutil.WriteFile(clientCA, clientCABytes, 0600); err != nil { + return "", errors.Wrapf(err, "failed to write client CA") + } + + return clientCA, nil +} + +func getHostnameAndIP(info envInfo) (string, string, error) { + ip := info.NodeIP + if ip == "" { + hostIP, err := net2.ChooseHostInterface() + if err != nil { + return "", "", err + } + ip = hostIP.String() + } + + name := info.NodeName + if name == "" { + hostname, err := os.Hostname() + if err != nil { + return "", "", err + } + hostname = strings.Split(hostname, ".")[0] + + d := md5.Sum([]byte(ip)) + name = hostname + "-" + hex.EncodeToString(d[:])[:8] + } + + return name, ip, nil +} + +func localAddress(controlConfig *config.Control) string { + return fmt.Sprintf("127.0.0.1:%d", controlConfig.AdvertisePort) +} + +func writeKubeConfig(envInfo *envInfo, info clientaccess.Info, controlConfig *config.Control, nodeCert *tls.Certificate) (string, error) { + os.MkdirAll(envInfo.DataDir, 0700) + kubeConfigPath := filepath.Join(envInfo.DataDir, "kubeconfig.yaml") + + info.URL = "https://" + localAddress(controlConfig) + info.CACerts = pem.EncodeToMemory(&pem.Block{ + Type: cert.CertificateBlockType, + Bytes: nodeCert.Certificate[1], + }) + + return kubeConfigPath, info.WriteKubeConfig(kubeConfigPath) +} + +func get() (*config.Node, error) { + envInfo, err := getEnvInfo() + if err != nil { + return nil, err + } + + serverURLParsed, err := url.Parse(envInfo.ServerURL) + if err != nil { + return nil, err + } + + info, err := clientaccess.ParseAndValidateToken(envInfo.ServerURL, envInfo.Token) + if err != nil { + return nil, err + } + + controlConfig, err := getConfig(info) + if err != nil { + return nil, err + } + + nodeCert, err := getNodeCert(info) + if err != nil { + return nil, err + } + + clientCA, err := writeNodeCA(envInfo.DataDir, nodeCert) + if err != nil { + return nil, err + } + + nodeName, nodeIP, err := getHostnameAndIP(*envInfo) + if err != nil { + return nil, err + } + + kubeConfig, err := writeKubeConfig(envInfo, *info, controlConfig, nodeCert) + if err != nil { + return nil, err + } + + nodeConfig := &controlConfig.NodeConfig + nodeConfig.LocalAddress = localAddress(controlConfig) + nodeConfig.AgentConfig.NodeIP = defString(nodeConfig.AgentConfig.NodeIP, nodeIP) + nodeConfig.AgentConfig.NodeName = defString(nodeConfig.AgentConfig.NodeName, nodeName) + nodeConfig.AgentConfig.CNIBinDir = defString(nodeConfig.AgentConfig.CNIBinDir, "/usr/share/cni") + nodeConfig.AgentConfig.CACertPath = clientCA + nodeConfig.AgentConfig.ListenAddress = defString(nodeConfig.AgentConfig.ListenAddress, "127.0.0.1") + nodeConfig.AgentConfig.KubeConfig = kubeConfig + nodeConfig.CACerts = info.CACerts + nodeConfig.ServerAddress = serverURLParsed.Host + nodeConfig.Certificate = nodeCert + if !nodeConfig.Docker { + nodeConfig.AgentConfig.RuntimeSocket = "/run/k3s/containerd.sock" + } + + return nodeConfig, nil +} + +func defString(val, newVal string) string { + if val == "" { + return newVal + } + return val +} + +func getConfig(info *clientaccess.Info) (*config.Control, error) { + data, err := clientaccess.Get("/v1-k3s/config", info) + if err != nil { + return nil, err + } + + controlControl := &config.Control{} + return controlControl, json.Unmarshal(data, controlControl) +} diff --git a/agent/containerd/containerd.go b/agent/containerd/containerd.go new file mode 100644 index 0000000000..414216bec4 --- /dev/null +++ b/agent/containerd/containerd.go @@ -0,0 +1,89 @@ +package containerd + +import ( + "context" + "fmt" + "os" + "os/exec" + "strings" + "syscall" + "time" + + "github.com/rancher/rio/agent/config" + util2 "github.com/rancher/rio/agent/util" + "github.com/sirupsen/logrus" + "google.golang.org/grpc" + runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2" + "k8s.io/kubernetes/pkg/kubelet/util" +) + +const ( + address = "/run/k3s/containerd.sock" + maxMsgSize = 1024 * 1024 * 16 + configToml = `[plugins.cri] + stream_server_address = "%NODE%" + stream_server_port = "10010" + [plugins.cri.cni] + bin_dir = "/usr/share/cni/bin" + conf_dir = "/etc/cni/net.d" +` +) + +func Run(ctx context.Context, config *config.NodeConfig) error { + args := []string{ + "containerd", + "-a", address, + "--state", "/run/k3s/containerd", + } + + if err := util2.WriteFile("/etc/containerd/config.toml", + strings.Replace(configToml, "%NODE%", config.AgentConfig.NodeName, -1)); err != nil { + return err + } + + if logrus.GetLevel() >= logrus.DebugLevel { + args = append(args, "--verbose") + } + + go func() { + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.SysProcAttr = &syscall.SysProcAttr{ + Pdeathsig: syscall.SIGKILL, + } + if err := cmd.Run(); err != nil { + fmt.Fprintf(os.Stderr, "containerd: %s\n", err) + } + os.Exit(1) + }() + + for { + addr, dailer, err := util.GetAddressAndDialer("unix://" + address) + if err != nil { + time.Sleep(1 * time.Second) + continue + } + + conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithTimeout(3*time.Second), grpc.WithDialer(dailer), grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxMsgSize))) + if err != nil { + time.Sleep(1 * time.Second) + continue + } + + c := runtimeapi.NewRuntimeServiceClient(conn) + + _, err = c.Version(ctx, &runtimeapi.VersionRequest{ + Version: "0.1.0", + }) + if err == nil { + conn.Close() + break + } + conn.Close() + logrus.Infof("Waiting for containerd startup") + time.Sleep(1 * time.Second) + } + + return nil +} diff --git a/agent/flannel/flannel.go b/agent/flannel/flannel.go new file mode 100644 index 0000000000..3a31dbfea5 --- /dev/null +++ b/agent/flannel/flannel.go @@ -0,0 +1,140 @@ +// Copyright 2015 flannel authors +// +// 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 flannel + +import ( + "fmt" + "net" + "os" + "path/filepath" + "sync" + + "github.com/coreos/flannel/backend" + "github.com/coreos/flannel/network" + "github.com/coreos/flannel/pkg/ip" + "github.com/coreos/flannel/subnet/kube" + "golang.org/x/net/context" + log "k8s.io/klog" + + // Backends need to be imported for their init() to get executed and them to register + _ "github.com/coreos/flannel/backend/vxlan" +) + +const ( + subnetFile = "/run/flannel/subnet.env" +) + +func flannel(ctx context.Context, kubeConfigFile string) error { + extIface, err := LookupExtIface() + if err != nil { + return err + } + + sm, err := kube.NewSubnetManager("", kubeConfigFile) + if err != nil { + return err + } + + config, err := sm.GetNetworkConfig(ctx) + if err != nil { + return err + } + + // Create a backend manager then use it to create the backend and register the network with it. + bm := backend.NewManager(ctx, sm, extIface) + + be, err := bm.GetBackend(config.BackendType) + if err != nil { + return err + } + + bn, err := be.RegisterNetwork(ctx, sync.WaitGroup{}, config) + if err != nil { + return err + } + + go network.SetupAndEnsureIPTables(network.MasqRules(config.Network, bn.Lease()), 60) + go network.SetupAndEnsureIPTables(network.ForwardRules(config.Network.String()), 50) + + if err := WriteSubnetFile(subnetFile, config.Network, true, bn); err != nil { + // Continue, even though it failed. + log.Warningf("Failed to write subnet file: %s", err) + } else { + log.Infof("Wrote subnet file to %s", subnetFile) + } + + // Start "Running" the backend network. This will block until the context is done so run in another goroutine. + log.Info("Running backend.") + bn.Run(ctx) + return nil +} + +func LookupExtIface() (*backend.ExternalInterface, error) { + var iface *net.Interface + var ifaceAddr net.IP + var err error + + log.Info("Determining IP address of default interface") + if iface, err = ip.GetDefaultGatewayIface(); err != nil { + return nil, fmt.Errorf("failed to get default interface: %s", err) + } + + ifaceAddr, err = ip.GetIfaceIP4Addr(iface) + if err != nil { + return nil, fmt.Errorf("failed to find IPv4 address for interface %s", iface.Name) + } + + log.Infof("Using interface with name %s and address %s", iface.Name, ifaceAddr) + + if iface.MTU == 0 { + return nil, fmt.Errorf("failed to determine MTU for %s interface", ifaceAddr) + } + + return &backend.ExternalInterface{ + Iface: iface, + IfaceAddr: ifaceAddr, + ExtAddr: ifaceAddr, + }, nil +} + +func WriteSubnetFile(path string, nw ip.IP4Net, ipMasq bool, bn backend.Network) error { + dir, name := filepath.Split(path) + os.MkdirAll(dir, 0755) + + tempFile := filepath.Join(dir, "."+name) + f, err := os.Create(tempFile) + if err != nil { + return err + } + + // Write out the first usable IP by incrementing + // sn.IP by one + sn := bn.Lease().Subnet + sn.IP += 1 + + fmt.Fprintf(f, "FLANNEL_NETWORK=%s\n", nw) + fmt.Fprintf(f, "FLANNEL_SUBNET=%s\n", sn) + fmt.Fprintf(f, "FLANNEL_MTU=%d\n", bn.MTU()) + _, err = fmt.Fprintf(f, "FLANNEL_IPMASQ=%v\n", ipMasq) + f.Close() + if err != nil { + return err + } + + // rename(2) the temporary file to the desired location so that it becomes + // atomically visible with the contents + return os.Rename(tempFile, path) + //TODO - is this safe? What if it's not on the same FS? +} diff --git a/agent/flannel/setup.go b/agent/flannel/setup.go new file mode 100644 index 0000000000..c3ffeba6d7 --- /dev/null +++ b/agent/flannel/setup.go @@ -0,0 +1,91 @@ +package flannel + +import ( + "context" + "strings" + "time" + + "github.com/rancher/rio/agent/util" + + "github.com/rancher/rio/agent/config" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +const ( + cniConf = `{ + "name":"cbr0", + "cniVersion":"0.3.1", + "plugins":[ + { + "type":"flannel", + "delegate":{ + "forceAddress":true, + "isDefaultGateway":true + } + }, + { + "type":"portmap", + "capabilities":{ + "portMappings":true + } + } + ] +} +` + netJson = `{ + "Network": "%CIDR%", + "Backend": { + "Type": "vxlan" + } +} +` +) + +func Run(ctx context.Context, config *config.NodeConfig) error { + nodeName := config.AgentConfig.NodeName + + restConfig, err := clientcmd.BuildConfigFromFlags("", config.AgentConfig.KubeConfig) + if err != nil { + return err + } + + client, err := kubernetes.NewForConfig(restConfig) + if err != nil { + return err + } + + for { + node, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{}) + if err == nil && node.Spec.PodCIDR != "" { + break + } + if err == nil { + logrus.Infof("waiting for node %s CIDR not assigned yet", nodeName) + } else { + logrus.Infof("waiting for node %s: %v", nodeName, err) + } + time.Sleep(2 * time.Second) + } + + if err := createCNIConf(); err != nil { + return err + } + + if err := createFlannelConf(config); err != nil { + return err + } + + return flannel(ctx, config.AgentConfig.KubeConfig) +} + +func createCNIConf() error { + return util.WriteFile("/etc/cni/net.d/10-flannel.conflist", cniConf) +} + +func createFlannelConf(config *config.NodeConfig) error { + return util.WriteFile("/etc/kube-flannel/net-conf.json", + strings.Replace(netJson, "%CIDR", config.AgentConfig.ClusterCIDR.String(), -1)) +} diff --git a/agent/main.go b/agent/main.go new file mode 100644 index 0000000000..fbfdbf60a1 --- /dev/null +++ b/agent/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + + "github.com/rancher/norman/signal" + "github.com/rancher/rio/agent/config" + "github.com/rancher/rio/agent/containerd" + "github.com/rancher/rio/agent/flannel" + "github.com/rancher/rio/agent/proxy" + "github.com/rancher/rio/agent/syssetup" + "github.com/rancher/rio/agent/tunnel" + "github.com/rancher/rio/pkg/daemons/agent" + "github.com/sirupsen/logrus" +) + +func main() { + if err := run(); err != nil { + logrus.Fatal(err) + } +} + +func run() error { + ctx := signal.SigTermCancelContext(context.Background()) + + nodeConfig := config.Get() + + if nodeConfig.Docker { + nodeConfig.AgentConfig.RuntimeSocket = "" + } else { + containerd.Run(ctx, nodeConfig) + } + + if err := syssetup.Configure(); err != nil { + return err + } + + if err := tunnel.Setup(nodeConfig); err != nil { + return err + } + + if err := proxy.Run(nodeConfig); err != nil { + return err + } + + if err := agent.Agent(&nodeConfig.AgentConfig); err != nil { + return err + } + + if !nodeConfig.NoFlannel { + if err := flannel.Run(ctx, nodeConfig); err != nil { + return err + } + } + + <-ctx.Done() + return ctx.Err() +} diff --git a/agent/proxy/proxy.go b/agent/proxy/proxy.go new file mode 100644 index 0000000000..c26003c468 --- /dev/null +++ b/agent/proxy/proxy.go @@ -0,0 +1,35 @@ +package proxy + +import ( + "crypto/tls" + "net/http" + + "github.com/pkg/errors" + "github.com/rancher/norman/pkg/proxy" + "github.com/rancher/rio/agent/config" + "github.com/sirupsen/logrus" +) + +func Run(config *config.NodeConfig) error { + proxy, err := proxy.NewSimpleProxy(config.ServerAddress, config.CACerts, true) + if err != nil { + return err + } + + listener, err := tls.Listen("tcp", config.LocalAddress, &tls.Config{ + Certificates: []tls.Certificate{ + *config.Certificate, + }, + }) + + if err != nil { + return errors.Wrap(err, "Failed to start tls listener") + } + + go func() { + err := http.Serve(listener, proxy) + logrus.Fatalf("TLS proxy stopped: %v", err) + }() + + return nil +} diff --git a/agent/syssetup/setup.go b/agent/syssetup/setup.go new file mode 100644 index 0000000000..280289ce73 --- /dev/null +++ b/agent/syssetup/setup.go @@ -0,0 +1,18 @@ +package syssetup + +import ( + "io/ioutil" + + "github.com/sirupsen/logrus" +) + +var ( + callIPTablesFile = "/proc/sys/net/bridge/bridge-nf-call-iptables" +) + +func Configure() error { + if err := ioutil.WriteFile(callIPTablesFile, []byte("1"), 0640); err != nil { + logrus.Warnf("failed to write value 1 at %s: %v", callIPTablesFile, err) + } + return nil +} diff --git a/agent/tunnel/tunnel.go b/agent/tunnel/tunnel.go new file mode 100644 index 0000000000..b9cf674513 --- /dev/null +++ b/agent/tunnel/tunnel.go @@ -0,0 +1,79 @@ +package tunnel + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/base64" + "fmt" + "net" + "net/http" + "sync" + "time" + + "github.com/gorilla/websocket" + "github.com/rancher/norman/pkg/remotedialer" + "github.com/rancher/rio/agent/config" + "github.com/sirupsen/logrus" + "k8s.io/client-go/tools/clientcmd" +) + +var ( + ports = map[string]bool{ + "10250": true, + "10010": true, + } +) + +func Setup(config *config.NodeConfig) error { + restConfig, err := clientcmd.BuildConfigFromFlags("", config.AgentConfig.KubeConfig) + if err != nil { + return err + } + + transportConfig, err := restConfig.TransportConfig() + if err != nil { + return err + } + + wsURL := fmt.Sprintf("wss://%s/v1/connect", config.ServerAddress) + headers := map[string][]string{ + "X-K3s-NodeName": {config.AgentConfig.NodeName}, + } + ws := &websocket.Dialer{} + + if len(config.CACerts) > 0 { + pool := x509.NewCertPool() + pool.AppendCertsFromPEM(config.CACerts) + ws.TLSClientConfig = &tls.Config{ + RootCAs: pool, + } + } + + if transportConfig.Username != "" { + auth := transportConfig.Username + ":" + transportConfig.Password + auth = base64.StdEncoding.EncodeToString([]byte(auth)) + headers["Authorization"] = []string{"Basic " + auth} + } + + once := sync.Once{} + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + for { + logrus.Infof("Connecting to %s", wsURL) + remotedialer.ClientConnect(wsURL, http.Header(headers), ws, func(proto, address string) bool { + host, port, err := net.SplitHostPort(address) + return err == nil && proto == "tcp" && ports[port] && host == "127.0.0.1" + }, func(_ context.Context) error { + once.Do(wg.Done) + return nil + }) + time.Sleep(5 * time.Second) + } + }() + + wg.Wait() + return nil +} diff --git a/agent/util/file.go b/agent/util/file.go new file mode 100644 index 0000000000..1e20e05c8a --- /dev/null +++ b/agent/util/file.go @@ -0,0 +1,18 @@ +package util + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +func WriteFile(name string, content string) error { + os.Mkdir(filepath.Dir(name), 0755) + err := ioutil.WriteFile(name, []byte(content), 0644) + if err != nil { + return errors.Wrapf(err, "writing %s", name) + } + return nil +} diff --git a/cli/cmd/agent/agent.go b/cli/cmd/agent/agent.go new file mode 100644 index 0000000000..6ba2cf522e --- /dev/null +++ b/cli/cmd/agent/agent.go @@ -0,0 +1,19 @@ +package agent + +import "github.com/urfave/cli" + +type Agent struct { + T_Token string `desc:"Token to use for authentication" env:"K3S_TOKEN"` + S_Server string `desc:"Server to connect to" env:"K3S_URL"` + D_DataDir string `desc:"Folder to hold state" default:"/var/lib/rancher/k3s"` + L_Log string `desc:"log to file"` + AgentShared +} + +type AgentShared struct { + I_NodeIp string `desc:"IP address to advertise for node"` +} + +func (a *Agent) Customize(command *cli.Command) { + command.Category = "CLUSTER RUNTIME" +} diff --git a/cli/cmd/agent/agent_k3s.go b/cli/cmd/agent/agent_k3s.go new file mode 100644 index 0000000000..12ae4f54ff --- /dev/null +++ b/cli/cmd/agent/agent_k3s.go @@ -0,0 +1,81 @@ +// +build k8s + +package agent + +import ( + "fmt" + "io" + "os" + "path/filepath" + "time" + + "github.com/natefinch/lumberjack" + "github.com/rancher/norman/pkg/clientaccess" + "github.com/rancher/norman/pkg/resolvehome" + "github.com/rancher/rio/pkg/enterchroot" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +func (a *Agent) Run(ctx *cli.Context) error { + if os.Getuid() != 0 { + return fmt.Errorf("agent must be ran as root") + } + + if len(a.T_Token) == 0 { + return fmt.Errorf("--token is required") + } + + if len(a.S_Server) == 0 { + return fmt.Errorf("--server is required") + } + + dataDir, err := resolvehome.Resolve(a.D_DataDir) + if err != nil { + return err + } + + return RunAgent(a.S_Server, a.T_Token, dataDir, a.L_Log, a.I_NodeIp) +} + +func RunAgent(server, token, dataDir, logFile, ipAddress string) error { + dataDir = filepath.Join(dataDir, "agent") + + for { + tmpFile, err := clientaccess.AgentAccessInfoToTempKubeConfig("", server, token) + if err != nil { + logrus.Error(err) + time.Sleep(2 * time.Second) + continue + } + os.Remove(tmpFile) + break + } + + os.Setenv("K3S_URL", server) + os.Setenv("K3S_TOKEN", token) + os.Setenv("K3S_DATA_DIR", dataDir) + os.Setenv("K3S_NODE_IP", ipAddress) + + os.MkdirAll(dataDir, 0700) + + stdout := io.Writer(os.Stdout) + stderr := io.Writer(os.Stderr) + + if logFile == "" { + stdout = os.Stdout + stderr = os.Stderr + } else { + l := &lumberjack.Logger{ + Filename: logFile, + MaxSize: 50, + MaxBackups: 3, + MaxAge: 28, + Compress: true, + } + stdout = l + stderr = l + } + + return enterchroot.Mount(filepath.Join(dataDir, "root"), stdout, stderr, os.Args[1:]) +} diff --git a/cli/cmd/agent/agent_none.go b/cli/cmd/agent/agent_none.go new file mode 100644 index 0000000000..8465ab302e --- /dev/null +++ b/cli/cmd/agent/agent_none.go @@ -0,0 +1,17 @@ +// +build !k8s + +package agent + +import ( + "fmt" + + "github.com/urfave/cli" +) + +func (a *Agent) Run(ctx *cli.Context) error { + return fmt.Errorf("agent support is not compiled in, add \"-tags k8s\" to \"go build\"") +} + +func RunAgent(server, token, dataDir, logFile string) error { + return fmt.Errorf("agent support is not compiled in, add \"-tags k8s\" to \"go build\"") +} diff --git a/cli/cmd/kubectl/kubectl.go b/cli/cmd/kubectl/kubectl.go new file mode 100644 index 0000000000..2067bb6125 --- /dev/null +++ b/cli/cmd/kubectl/kubectl.go @@ -0,0 +1,24 @@ +package kubectl + +import ( + "os" + + "github.com/rancher/rio/pkg/kubectl" + "github.com/urfave/cli" +) + +func NewKubectlCommand() cli.Command { + return cli.Command{ + Name: "kubectl", + Usage: "Run kubectl", + SkipFlagParsing: true, + SkipArgReorder: true, + Action: run, + } +} + +func run(ctx *cli.Context) error { + os.Args = append([]string{"kubectl"}, ctx.Args()...) + kubectl.Main() + return nil +} diff --git a/cli/cmd/server/server.go b/cli/cmd/server/server.go new file mode 100644 index 0000000000..3532d46574 --- /dev/null +++ b/cli/cmd/server/server.go @@ -0,0 +1,128 @@ +package server + +import ( + "context" + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/docker/docker/pkg/reexec" + "github.com/natefinch/lumberjack" + "github.com/rancher/norman/signal" + "github.com/rancher/rio/pkg/server" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" + "k8s.io/apimachinery/pkg/util/net" +) + +var ( + appName = filepath.Base(os.Args[0]) + + config server.Config + log string +) + +var ServerCommand = cli.Command{ + Name: "server", + Usage: "Run management server", + UsageText: appName + " server [OPTIONS]", + Action: Run, + Flags: []cli.Flag{ + cli.IntFlag{ + Name: "https-listen-port", + Usage: "HTTPS listen port", + Value: 6443, + Destination: &config.TLSConfig.HTTPSPort, + }, + cli.IntFlag{ + Name: "http-listen-port", + Usage: "HTTP listen port (for /healthz, HTTPS redirect, and port for TLS terminating LB)", + Value: 0, + Destination: &config.TLSConfig.HTTPPort, + }, + cli.StringFlag{ + Name: "data-dir", + Usage: "Folder to hold state default /var/lib/rancher/k3s or ${HOME}/.rancher/k3s if not root", + Destination: &config.ControlConfig.DataDir, + }, + //cli.StringFlag{ + // Name: "advertise-address", + // Usage: "Address of the server to put in the generated kubeconfig", + // Destination: &config.AdvertiseIP, + //}, + cli.BoolFlag{ + Name: "disable-agent", + Usage: "Do not run a local agent and register a local kubelet", + Destination: &config.DisableAgent, + }, + cli.StringFlag{ + Name: "log", + Usage: "Log to file", + Destination: &log, + }, + }, +} + +func setupLogging(app *cli.Context) { + if !app.GlobalBool("debug") { + flag.Set("stderrthreshold", "3") + flag.Set("alsologtostderr", "false") + flag.Set("logtostderr", "false") + } +} + +func runWithLogging(app *cli.Context) error { + l := &lumberjack.Logger{ + Filename: log, + MaxSize: 50, + MaxBackups: 3, + MaxAge: 28, + Compress: true, + } + + args := append([]string{"k3s"}, os.Args[1:]...) + cmd := reexec.Command(args...) + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, "_RIO_REEXEC_=true") + cmd.Stderr = l + cmd.Stdout = l + cmd.Stdin = os.Stdin + return cmd.Run() +} + +func Run(app *cli.Context) error { + if log != "" && os.Getenv("_RIO_REEXEC_") == "" { + return runWithLogging(app) + } + + setupLogging(app) + + if !config.DisableAgent && os.Getuid() != 0 { + return fmt.Errorf("must run as root unless --disable-agent is specified") + } + + if config.ControlConfig.NodeConfig.AgentConfig.NodeIP == "" { + ip, err := net.ChooseHostInterface() + if err == nil { + config.ControlConfig.NodeConfig.AgentConfig.NodeIP = ip.String() + } + } + + logrus.Info("Starting k3s ", app.App.Version) + ctx := signal.SigTermCancelContext(context.Background()) + if err := server.StartServer(ctx, &config); err != nil { + return err + } + + if config.DisableAgent { + <-ctx.Done() + return nil + } + + return nil + //logFile := filepath.Join(serverConfig.DataDir, "agent/agent.log") + //url := fmt.Sprintf("https://localhost:%d", httpsListenPort) + //logrus.Infof("Agent starting, logging to %s", logFile) + //return agent.RunAgent(url, server2.FormatToken(serverConfig.Runtime.NodeToken), serverConfig.DataDir, logFile, "") +} diff --git a/cli/pkg/builder/builder.go b/cli/pkg/builder/builder.go new file mode 100644 index 0000000000..fcce557f2e --- /dev/null +++ b/cli/pkg/builder/builder.go @@ -0,0 +1,151 @@ +package builder + +import ( + "fmt" + "reflect" + "regexp" + "strconv" + "strings" + "unsafe" + + "github.com/urfave/cli" +) + +var ( + caseRegexp = regexp.MustCompile("([a-z])([A-Z])") +) + +type clirunnable interface { + Run(app *cli.Context) error +} + +type customizer interface { + Customize(cmd *cli.Command) +} + +type fieldInfo struct { + FieldType reflect.StructField + FieldValue reflect.Value +} + +func fields(obj interface{}) []fieldInfo { + ptrValue := reflect.ValueOf(obj) + objValue := ptrValue.Elem() + + var result []fieldInfo + + for i := 0; i < objValue.NumField(); i++ { + fieldType := objValue.Type().Field(i) + if fieldType.Anonymous && fieldType.Type.Kind() == reflect.Struct { + result = append(result, fields(objValue.Field(i).Addr().Interface())...) + } else if !fieldType.Anonymous { + result = append(result, fieldInfo{ + FieldValue: objValue.Field(i), + FieldType: objValue.Type().Field(i), + }) + } + } + + return result +} + +func Command(obj interface{}, usage, usageText, description string) cli.Command { + slices := map[string]reflect.Value{} + maps := map[string]reflect.Value{} + ptrValue := reflect.ValueOf(obj) + objValue := ptrValue.Elem() + + c := cli.Command{ + Name: strings.ToLower(strings.Replace(objValue.Type().Name(), "Command", "", 1)), + Usage: usage, + UsageText: usageText, + Description: description, + UseShortOptionHandling: true, + SkipArgReorder: true, + } + + for _, info := range fields(obj) { + defMessage := "" + fieldType := info.FieldType + v := info.FieldValue + + switch fieldType.Type.Kind() { + case reflect.Int: + flag := cli.IntFlag{ + Name: name(fieldType.Name), + Usage: fieldType.Tag.Get("desc"), + EnvVar: fieldType.Tag.Get("env"), + Destination: (*int)(unsafe.Pointer(v.Addr().Pointer())), + } + defValue := fieldType.Tag.Get("default") + if defValue != "" { + n, err := strconv.Atoi(defValue) + if err != nil { + panic("bad default " + defValue + " on field " + fieldType.Name) + } + flag.Value = n + } + c.Flags = append(c.Flags, flag) + case reflect.String: + flag := cli.StringFlag{ + Name: name(fieldType.Name), + Usage: fieldType.Tag.Get("desc"), + Value: fieldType.Tag.Get("default"), + EnvVar: fieldType.Tag.Get("env"), + Destination: (*string)(unsafe.Pointer(v.Addr().Pointer())), + } + c.Flags = append(c.Flags, flag) + case reflect.Slice: + slices[name(fieldType.Name)] = v + defMessage = " " + fallthrough + case reflect.Map: + if defMessage == "" { + maps[name(fieldType.Name)] = v + defMessage = " " + } + flag := cli.StringSliceFlag{ + Name: name(fieldType.Name), + Usage: fieldType.Tag.Get("desc") + defMessage, + EnvVar: fieldType.Tag.Get("env"), + Value: &cli.StringSlice{}, + } + c.Flags = append(c.Flags, flag) + case reflect.Bool: + flag := cli.BoolFlag{ + Name: name(fieldType.Name), + Usage: fieldType.Tag.Get("desc"), + EnvVar: fieldType.Tag.Get("env"), + Destination: (*bool)(unsafe.Pointer(v.Addr().Pointer())), + } + c.Flags = append(c.Flags, flag) + default: + panic("Unknown kind on field " + fieldType.Name + " on " + objValue.Type().Name()) + } + } + + if run, ok := obj.(clirunnable); ok { + c.Action = run.Run + } else { + panic(fmt.Sprintf("failed to find Action function for %T", obj)) + } + + cust, ok := obj.(customizer) + if ok { + cust.Customize(&c) + } + + return c +} + +func name(name string) string { + parts := strings.Split(name, "_") + i := len(parts) - 1 + name = caseRegexp.ReplaceAllString(parts[i], "$1-$2") + name = strings.ToLower(name) + result := append([]string{name}, parts[0:i]...) + for i := 0; i < len(result); i++ { + result[i] = strings.ToLower(result[i]) + } + return strings.Join(result, ",") +} diff --git a/image/Dockerfile b/image/Dockerfile new file mode 100644 index 0000000000..dbcd9204ed --- /dev/null +++ b/image/Dockerfile @@ -0,0 +1,179 @@ +### BASE ### +FROM alpine:3.8 as base +RUN apk -U add findutils iptables ipset bash ca-certificates jq iproute2 nfs-utils coreutils libseccomp conntrack-tools +# RUN apk -U add eudev tinyssh e2fsprogs mdadm rsync nfs-utils parted + +FROM base as k3s-build +COPY --from=base /bin /usr/src/image/bin/ +COPY --from=base /lib /usr/src/image/lib/ +COPY --from=base /sbin /usr/src/image/sbin/ +COPY --from=base /etc/ssl/certs/ca-certificates.crt /usr/src/image/etc/ssl/certs/ca-certificates.crt +COPY --from=base /etc/terminfo /usr/src/image/etc/terminfo +COPY --from=base /usr /usr/src/image/usr/ + +WORKDIR /usr/src/image + +RUN rm -rf usr/bin/iconv \ + usr/bin/scanelf \ + usr/bin/ssl_client \ + usr/bin/pkgconf \ + usr/bin/getent \ + usr/bin/locate \ + usr/bin/updatedb \ + usr/bin/c_rehash \ + usr/bin/getconf \ + usr/etc \ + usr/include \ + usr/lib/bash \ + usr/lib/krb5 \ + usr/lib/pkgconfig \ + usr/lib/tc \ + usr/libexec \ + usr/local \ + usr/sbin/nfsiostat \ + usr/sbin/rpc.gssd \ + usr/sbin/nfsidmap \ + usr/sbin/blkmapd \ + usr/sbin/conntrackd \ + usr/sbin/nfct \ + usr/sbin/nfsstat \ + usr/sbin/mountstats \ + usr/sbin/setcap \ + usr/sbin/exportfs \ + usr/sbin/update-ca-certificates \ + usr/sbin/capsh \ + usr/sbin/getcap \ + usr/sbin/rpcdebug \ + usr/sbin/start-statd \ + usr/sbin/getpcaps \ + usr/sbin/sm-notify \ + usr/share/aclocal \ + usr/share/apk \ + usr/share/ca-certificates \ + usr/share/man \ + usr/share/misc && \ + find usr/share/terminfo -type f -exec rm {} \; && \ + ln -s xterm-color usr/share/terminfo/x/xterm-256color && \ + rmdir usr/share/terminfo/* || true +RUN rm -rf bin/sh \ + lib/apk \ + lib/mdev \ + sbin/ss \ + sbin/routel \ + sbin/*-compat* \ + sbin/genl \ + sbin/lnstat \ + sbin/ifstat \ + sbin/mkmntdirs \ + sbin/nfsdcltrack \ + sbin/rtacct \ + sbin/nstat \ + sbin/routef \ + sbin/apk \ + sbin/tc \ + sbin/ifcfg \ + sbin/setup-udev \ + sbin/rtpr \ + sbin/osd_login \ + sbin/bridge \ + sbin/rtmon && \ + ln -s bash bin/sh && \ + mkdir -p lib/modules + +RUN mv sbin/* bin/ && \ + rmdir sbin && \ + ln -s bin sbin + +RUN mkdir lib2 && \ + mv usr/lib/* lib2/ && \ + mv lib2/* lib/ && \ + mv usr/bin/* bin/ && \ + mv usr/sbin/* bin/ && \ + mv usr/share . && \ + rm -rf usr lib2 && \ + for i in $(ls -l bin | grep usr/bin/coreutils | awk '{print $(NF-2)}'); do \ + rm bin/$i && ln -s coreutils bin/$i \ + ;done && \ + find -L bin -type l -exec rm {} \; -print + +RUN apk add upx && \ + upx $(find bin -type f -executable \! -name coreutils) || true + +RUN echo '#### LAYOUT #####' && \ + find -type d && \ + echo '#### BIN #####' && \ + find bin -type f -executable && \ + du -x -s -h + +RUN tar cf ../rootfs.tar * && \ + ls -la ../rootfs.tar + +CMD ["sh"] + +### BUILD IMAGE ### +FROM golang:1.11-alpine AS gobuild +RUN apk -U add git gcc linux-headers musl-dev make libseccomp libseccomp-dev bash +RUN rm -f /bin/sh && ln -s /bin/bash /bin/sh + +### CNI ### +FROM gobuild AS cni +RUN mkdir -p $GOPATH/src/github.com/containernetworking && \ + cd $GOPATH/src/github.com/containernetworking && \ + git clone https://github.com/ibuildthecloud/plugins.git && \ + cd plugins && \ + git checkout 9810b7d5137b171c4e07ce59bb18be9feccec557 +RUN go build -buildmode=pie -ldflags -s -o /usr/bin/cni github.com/containernetworking/plugins + +### RUNC ### +FROM gobuild AS runc +RUN go get -d github.com/opencontainers/runc && \ + git -C $GOPATH/src/github.com/opencontainers/runc checkout -b build v1.0.0-rc5 +WORKDIR $GOPATH/src/github.com/opencontainers/runc +RUN make runc && \ + cp runc /usr/bin/ + +### CONTAINERD ### +FROM gobuild AS containerd +RUN go get -d github.com/containerd/containerd && \ + git -C $GOPATH/src/github.com/containerd/containerd checkout -b build v1.1.4 +WORKDIR $GOPATH/src/github.com/containerd/containerd +RUN sed -i -e '/aufs/d' -e '/zfs/d' cmd/containerd/builtins_linux.go +RUN make BUILDTAGS="apparmor seccomp no_btrfs netgo osusergo" bin/containerd bin/containerd-shim && \ + cp bin/containerd bin/containerd-shim /usr/bin/ + +### AGENT ### +FROM gobuild AS agent +ADD /build/vendor.tar $GOPATH/src/github.com/rancher/rio/ +COPY /agent $GOPATH/src/github.com/rancher/rio/agent +WORKDIR $GOPATH/src/github.com/rancher/rio +RUN go build -buildmode=pie -tags k3s -ldflags -s -o /usr/bin/agent ./agent + +### ASSEMBLE IMAGE ### +FROM gobuild + +RUN apk add -U squashfs-tools + +COPY --from=k3s-build /usr/src/rootfs.tar /usr/src/rootfs.tar +RUN mkdir /usr/src/image && \ + tar xf /usr/src/rootfs.tar -C /usr/src/image + +COPY --from=runc /usr/bin/runc /usr/src/image/bin/runc +RUN strip --strip-unneeded /usr/src/image/bin/runc + +COPY --from=agent /usr/bin/agent /usr/src/image/bin/ +RUN strip --strip-unneeded /usr/src/image/bin/agent + +COPY --from=containerd /usr/bin/containerd-shim /usr/bin/containerd /usr/src/image/bin/ +#COPY containerd /usr/src/image/bin +RUN strip --strip-unneeded /usr/src/image/bin/containerd /usr/src/image/bin/containerd-shim + +RUN mkdir -p /usr/src/image/share/cni/bin +COPY --from=cni /usr/bin/cni /usr/src/image/share/cni/bin +RUN cd /usr/src/image/share/cni/bin && \ + for i in ./bridge ./flannel ./host-local ./loopback ./portmap; do \ + ln -s cni $i \ + ;done + +COPY image/init /usr/src/image/init + +RUN mksquashfs /usr/src/image main.squashfs diff --git a/image/build b/image/build new file mode 100755 index 0000000000..df568fb951 --- /dev/null +++ b/image/build @@ -0,0 +1,19 @@ +#!/bin/bash +set -e + +cd $(dirname $0) +rm -rf main.squashfs + +pushd .. +mkdir -p build +echo Copying vendor +tar chf build/vendor.tar \ + --exclude vendor/github.com/coreos/etcd/cmd \ + --exclude vendor/github.com/jteeuwen/go-bindata/testdata \ + --exclude vendor/github.com/karrick/godirwalk/testdata \ + vendor +echo Running agent docker build +docker build -t bb -f image/Dockerfile . +popd + +docker run --rm bb cat main.squashfs > main.squashfs diff --git a/image/init b/image/init new file mode 100755 index 0000000000..2877a9cf49 --- /dev/null +++ b/image/init @@ -0,0 +1,147 @@ +#!/usr/bin/sh +set -e + +if [ "$ENTER_DEBUG" = true ]; then + set -x +fi + +layout() +{ + mount --make-rshared / + + mkdir -p /proc + mount -t proc -o nodev,nosuid,noexec,relatime none /proc + + for i in cache empty lib local local log opt spool tmp; do + mkdir -p /var/$i + done + + for i in run dev home mnt media opt root lib/modules lib/firmware var/lib/docker; do + if [ -d /.root/$i ]; then + mkdir -p /$i + mount --rbind /.root/$i /$i + fi + done + + if [ -L /.root/var/run ]; then + ln -sf /run /var/run + else + mkdir -p /var/run + mount --rbind /.root/var/run /var/run + fi + + mkdir -p $K3S_DATA_DIR + mount --rbind /.root/$K3S_DATA_DIR /$K3S_DATA_DIR + + mkdir -p /run/k3s/containerd +} + +sysfs() +{ + mkdir -p /sys + mount -t sysfs none /sys + + mount -t securityfs -o noexec,nosuid,nodev none /sys/kernel/security 2>/dev/null|| true + mount -t configfs -o noexec,nosuid,nodev none /sys/kernel/config 2>/dev/null || true + mount -t fusectl -o noexec,nosuid,nodev none /sys/fs/fuse/connections 2>/dev/null || true + mount -t binfmt_misc -o noexec,nosuid,nodev none /proc/sys/fs/binfmt_misc 2>/dev/null || true +} + +cgroups() +{ + mount -t tmpfs -o mode=755,size=10m none /sys/fs/cgroup + cat /proc/cgroups > /tmp/cgroups + + for i in $(seq 0 20); do + t="" + l="$(cat /tmp/cgroups | grep '1$' | awk '{print $1 " " $2}' | grep -w $i | awk '{print $1}')" + for j in $l; do + if [ -z "$t" ]; then + t=$j + else + t="${t},$j" + fi + done + + if [ -z "$t" ]; then + continue + fi + + mkdir -p /sys/fs/cgroup/${t} + mount -t cgroup -o ${t},noexec,nosuid,nodev none /sys/fs/cgroup/${t} + mkdir -p /sys/fs/cgroup/${t}/k3s + + for j in $l; do + if [ $j != $t ]; then + ln -s $t /sys/fs/cgroup/$j + fi + done + done + + # good ole systemd + mkdir -p /sys/fs/cgroup/systemd + mount -t cgroup -o none,name=systemd cgroup /sys/fs/cgroup/systemd + mkdir -p /sys/fs/cgroup/systemd/k3s + + rm /tmp/cgroups +} + +mketc() +{ + mkdir -p /etc + cp -rf usr/etc/* /etc/ + for i in /.root/usr/lib/os-release /.root/etc/os-release; do + if [ -e $i ]; then + cp -f $i /etc/os-release + fi + done + + if [ -e /.root/etc/machine-id ]; then + cp -f /.root/etc/machine-id /etc/machine-id + fi + + hostname > /etc/hostname + + cat > /etc/hosts << EOF +127.0.0.1 localhost $NODE_NAME $(hostname) +::1 localhost ip6-localhost ip6-loopback +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +EOF + + cat > /etc/resolv.conf << EOF +nameserver 1.1.1.1 +EOF +} + +nodename() +{ + if [ ! -e $K3S_DATA_DIR/id ]; then + echo $RANDOM > $K3S_DATA_DIR/id + fi + export NODE_NAME="$(hostname | cut -f1 -d.)-$(<$K3S_DATA_DIR/id)" +} + +layout +nodename +mketc +sysfs +cgroups + +umount -l .root +rmdir .root + +if [ "$1" = "--" ]; then + shift 1 + exec "$@" +fi + +exec env -i -- \ + HOME=/root \ + NODE_NAME=$NODE_NAME \ + PATH=/sbin:/bin \ + K3S_DATA_DIR=$K3S_DATA_DIR \ + K3S_NODE_IP=$K3S_NODE_IP \ + K3S_TOKEN=$K3S_TOKEN \ + K3S_URL=$K3S_URL \ + agent diff --git a/main.go b/main.go new file mode 100644 index 0000000000..ea26f18c4a --- /dev/null +++ b/main.go @@ -0,0 +1,71 @@ +//go:generate go run types/codegen/cleanup/main.go +//go:generate go run types/codegen/main.go + +package main + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/docker/docker/pkg/reexec" + "github.com/rancher/rio/cli/cmd/agent" + "github.com/rancher/rio/cli/cmd/kubectl" + "github.com/rancher/rio/cli/cmd/server" + "github.com/rancher/rio/cli/pkg/builder" + "github.com/rancher/rio/version" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" + + _ "github.com/rancher/rio/pkg/kubectl" +) + +var ( + appName = filepath.Base(os.Args[0]) + debug bool +) + +func main() { + old := os.Args[0] + os.Args[0] = filepath.Base(os.Args[0]) + if reexec.Init() { + return + } + os.Args[0] = old + + app := cli.NewApp() + app.Name = appName + app.Usage = "Kubernetes, but small and simple" + app.Version = version.Version + cli.VersionPrinter = func(c *cli.Context) { + fmt.Printf("%s version %s\n", app.Name, app.Version) + } + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "debug", + Usage: "Turn on debug logs", + Destination: &debug, + }, + } + + app.Commands = []cli.Command{ + server.ServerCommand, + builder.Command(&agent.Agent{}, + "Run node agent", + appName+" agent [OPTIONS]", + ""), + + kubectl.NewKubectlCommand(), + } + app.Before = func(ctx *cli.Context) error { + if debug { + logrus.SetLevel(logrus.DebugLevel) + } + return nil + } + + err := app.Run(os.Args) + if err != nil { + logrus.Fatal(err) + } +} diff --git a/package/Dockerfile b/package/Dockerfile new file mode 100644 index 0000000000..2beda04e6f --- /dev/null +++ b/package/Dockerfile @@ -0,0 +1,5 @@ +FROM alpine:3.8 +RUN apk -U --no-cache add ca-certificates + +COPY rio / +WORKDIR /var/lib/rancher/rio diff --git a/pkg/controller/tls/handler.go b/pkg/controller/tls/handler.go new file mode 100644 index 0000000000..7dfa0d20f2 --- /dev/null +++ b/pkg/controller/tls/handler.go @@ -0,0 +1,12 @@ +package tls + +import ( + "context" + + v1 "github.com/rancher/rio/types/apis/k3s.cattle.io/v1" +) + +func Register(ctx context.Context) error { + clients := v1.ClientsFrom(ctx) + clients.ListenerConfig.OnChange(ctx) +} diff --git a/pkg/daemons/agent/agent.go b/pkg/daemons/agent/agent.go new file mode 100644 index 0000000000..1dff5881d9 --- /dev/null +++ b/pkg/daemons/agent/agent.go @@ -0,0 +1,98 @@ +package agent + +import ( + "context" + "math/rand" + "time" + + "github.com/rancher/rio/pkg/daemons/config" + + "github.com/sirupsen/logrus" + "k8s.io/apiserver/pkg/util/logs" + app2 "k8s.io/kubernetes/cmd/kube-proxy/app" + "k8s.io/kubernetes/cmd/kubelet/app" + _ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration + _ "k8s.io/kubernetes/pkg/version/prometheus" // for version metric registration +) + +func Agent(config *config.Agent) error { + rand.Seed(time.Now().UTC().UnixNano()) + + prepare(config) + + kubelet(config) + kubeProxy(config) + + return nil +} + +func prepare(config *config.Agent) { + if config.CNIBinDir == "" { + config.CNIBinDir = "/opt/cni/bin" + } + if config.CNIConfDir == "" { + config.CNIConfDir = "/etc/cni/net.d" + } +} + +func kubeProxy(config *config.Agent) { + args := []string{ + "--proxy-mode", "iptables", + "--healthz-bind-address", "127.0.0.1", + "--kubeconfig", config.KubeConfig, + "--cluster-cidr", config.ClusterCIDR.String(), + } + args = append(args, config.ExtraKubeletArgs...) + + command := app2.NewProxyCommand() + command.SetArgs(args) + go func() { + err := command.Execute() + logrus.Fatalf("kube-proxy exited: %v", err) + }() +} + +func kubelet(config *config.Agent) { + command := app.NewKubeletCommand(context.Background().Done()) + logs.InitLogs() + defer logs.FlushLogs() + + args := []string{ + "--healthz-bind-address", "127.0.0.1", + "--read-only-port", "0", + "--allow-privileged=true", + "--cluster-domain", "cluster.local", + "--kubeconfig", config.KubeConfig, + "--eviction-hard", "imagefs.available<5%,nodefs.available<5%", + "--eviction-minimum-reclaim", "imagefs.available=10%,nodefs.available=10%", + "--feature-gates=MountPropagation=true", + "--node-ip", config.NodeIP, + "--fail-swap-on=false", + "--cgroup-root", "/k3s", + "--cgroup-driver", "cgroupfs", + "--cni-conf-dir", config.CNIConfDir, + "--cni-bin-dir", config.CNIBinDir, + } + if len(config.ClusterDNS) > 0 { + args = append(args, "--cluster-dns", config.ClusterDNS.String()) + } + if config.RuntimeSocket != "" { + args = append(args, "--container-runtime-endpoint", config.RuntimeSocket) + } + if config.ListenAddress != "" { + args = append(args, "--address", config.ListenAddress) + } + if config.CACertPath != "" { + args = append(args, "--anonymous-auth=false", "--client-ca-file", config.CACertPath) + } + if config.NodeName != "" { + args = append(args, "--hostname-override", config.NodeName) + } + args = append(args, config.ExtraKubeletArgs...) + + command.SetArgs(args) + + go func() { + logrus.Fatalf("kubelet exited: %v", command.Execute()) + }() +} diff --git a/pkg/daemons/config/types.go b/pkg/daemons/config/types.go new file mode 100644 index 0000000000..9520a6534f --- /dev/null +++ b/pkg/daemons/config/types.go @@ -0,0 +1,74 @@ +package config + +import ( + "crypto/tls" + "net" + "net/http" + + "k8s.io/apiserver/pkg/authentication/authenticator" +) + +type Node struct { + Docker bool + NoFlannel bool + NoCoreDNS bool + LocalAddress string + AgentConfig Agent + CACerts []byte + ServerAddress string + Certificate *tls.Certificate +} + +type Agent struct { + NodeName string + ClusterCIDR net.IPNet + ClusterDNS net.IP + KubeConfig string + NodeIP string + RuntimeSocket string + ListenAddress string + CACertPath string + CNIBinDir string + CNIConfDir string + ExtraKubeletArgs []string + ExtraKubeProxyArgs []string +} + +type Control struct { + AdvertisePort int + ListenPort int + ClusterIPRange *net.IPNet + ServiceIPRange *net.IPNet + DataDir string + ETCDEndpoints []string + ETCDKeyFile string + ETCDCertFile string + ETCDCAFile string + NoScheduler bool + ExtraAPIArgs []string + ExtraControllerArgs []string + ExtraSchedulerAPIArgs []string + NodeConfig Node + + Runtime *ControlRuntime `json:"-"` +} + +type ControlRuntime struct { + TLSCert string + TLSKey string + TLSCA string + TLSCAKey string + TokenCA string + TokenCAKey string + ServiceKey string + PasswdFile string + KubeConfigSystem string + + NodeCert string + NodeKey string + ClientToken string + NodeToken string + Handler http.Handler + Tunnel http.Handler + Authenticator authenticator.Request +} diff --git a/pkg/daemons/control/server.go b/pkg/daemons/control/server.go new file mode 100644 index 0000000000..9d7d790b25 --- /dev/null +++ b/pkg/daemons/control/server.go @@ -0,0 +1,560 @@ +package control + +import ( + "context" + cryptorand "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/csv" + "encoding/hex" + "fmt" + "html/template" + "io" + "io/ioutil" + "math/rand" + "net" + "net/http" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/rancher/rio/pkg/daemons/config" + + _ "github.com/mattn/go-sqlite3" // sqlite + "github.com/sirupsen/logrus" + "k8s.io/apiserver/pkg/authentication/authenticator" + certutil "k8s.io/client-go/util/cert" + "k8s.io/kubernetes/cmd/kube-apiserver/app" + cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app" + sapp "k8s.io/kubernetes/cmd/kube-scheduler/app" + _ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration + "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes" + "k8s.io/kubernetes/pkg/master" + _ "k8s.io/kubernetes/pkg/util/reflector/prometheus" // for reflector metric registration + _ "k8s.io/kubernetes/pkg/util/workqueue/prometheus" // for workqueue metric registration + _ "k8s.io/kubernetes/pkg/version/prometheus" // for version metric registration +) + +var ( + localhostIP = net.ParseIP("127.0.0.1") + kubeconfigTemplate = template.Must(template.New("kubeconfig").Parse(`apiVersion: v1 +clusters: +- cluster: + server: {{.URL}} + certificate-authority-data: {{.CACert}} + name: local +contexts: +- context: + cluster: local + namespace: default + user: user + name: Default +current-context: Default +kind: Config +preferences: {} +users: +- name: user + user: + username: {{.User}} + password: {{.Password}} +`)) +) + +func Server(ctx context.Context, cfg *config.Control) error { + rand.Seed(time.Now().UTC().UnixNano()) + + runtime := &config.ControlRuntime{} + cfg.Runtime = runtime + + if err := prepare(cfg, runtime); err != nil { + return err + } + + cfg.Runtime.Tunnel = setupTunnel() + + auth, handler, err := apiServer(ctx, cfg, runtime) + if err != nil { + return err + } + + runtime.Handler = handler + runtime.Authenticator = auth + + if !cfg.NoScheduler { + scheduler(cfg, runtime) + } + + controllerManager(cfg, runtime) + + return nil +} + +func controllerManager(config *config.Control, runtime *config.ControlRuntime) { + args := []string{ + "--kubeconfig", runtime.KubeConfigSystem, + "--leader-elect=true", + "--service-account-private-key-file", runtime.ServiceKey, + "--allocate-node-cidrs", + "--cluster-cidr", config.ClusterIPRange.String(), + "--root-ca-file", runtime.TokenCA, + "--port", "0", + "--secure-port", "0", + } + args = append(args, config.ExtraControllerArgs...) + command := cmapp.NewControllerManagerCommand() + command.SetArgs(args) + + go func() { + logrus.Infof("Running kube-controller-manager %s", argString(args)) + logrus.Fatalf("controller-manager exited: %v", command.Execute()) + }() +} + +type argString []string + +func (a argString) String() string { + b := strings.Builder{} + for _, s := range a { + if b.Len() > 0 { + b.WriteString(" ") + } + b.WriteString(s) + } + return b.String() +} + +func scheduler(config *config.Control, runtime *config.ControlRuntime) { + args := []string{ + "--kubeconfig", runtime.KubeConfigSystem, + "--port", "0", + "--secure-port", "0", + } + args = append(args, config.ExtraSchedulerAPIArgs...) + command := sapp.NewSchedulerCommand() + command.SetArgs(args) + + go func() { + logrus.Infof("Running kube-scheduler %s", argString(args)) + logrus.Fatalf("scheduler exited: %v", command.Execute()) + }() +} + +func apiServer(ctx context.Context, config *config.Control, runtime *config.ControlRuntime) (authenticator.Request, http.Handler, error) { + var args []string + + if len(config.ETCDEndpoints) > 0 { + args = append(args, "--storage-backend", "etcd3") + args = append(args, "--etcd-servers", strings.Join(config.ETCDEndpoints, ",")) + if config.ETCDKeyFile != "" { + args = append(args, "--etcd-keyfile", config.ETCDKeyFile) + } + if config.ETCDCAFile != "" { + args = append(args, "--etcd-cafile", config.ETCDCAFile) + } + if config.ETCDCertFile != "" { + args = append(args, "--etcd-certfile", config.ETCDCertFile) + } + } + + args = append(args, "--allow-privileged=true") + args = append(args, "--authorization-mode", strings.Join([]string{modes.ModeNode, modes.ModeRBAC}, ",")) + args = append(args, "--service-account-signing-key-file", runtime.ServiceKey) + args = append(args, "--service-cluster-ip-range", config.ServiceIPRange.String()) + args = append(args, "--advertise-port", strconv.Itoa(config.AdvertisePort)) + args = append(args, "--advertise-address", localhostIP.String()) + args = append(args, "--insecure-port", "0") + args = append(args, "--secure-port", strconv.Itoa(config.ListenPort)) + args = append(args, "--bind-address", localhostIP.String()) + args = append(args, "--tls-cert-file", runtime.TLSCert) + args = append(args, "--tls-private-key-file", runtime.TLSKey) + args = append(args, "--service-account-key-file", runtime.ServiceKey) + args = append(args, "--service-account-issuer", "k3s") + args = append(args, "--api-audiences", "unknown") + args = append(args, "--basic-auth-file", runtime.PasswdFile) + //args = append(args, "--kubelet-client-certificate", runtime.NodeCert) + //args = append(args, "--kubelet-client-key", runtime.NodeKey) + + args = append(args, config.ExtraAPIArgs...) + command := app.NewAPIServerCommand(ctx.Done()) + command.SetArgs(args) + + go func() { + logrus.Infof("Running kube-apiserver %s", argString(args)) + logrus.Fatalf("apiserver exited: %v", command.Execute()) + }() + + startupConfig := <-app.StartupConfig + + return startupConfig.Authenticator, startupConfig.Handler, nil +} + +func defaults(config *config.Control) { + if config.ClusterIPRange == nil { + _, clusterIPNet, _ := net.ParseCIDR("10.42.0.0/16") + config.ClusterIPRange = clusterIPNet + } + + if config.ServiceIPRange == nil { + _, serviceIPNet, _ := net.ParseCIDR("10.43.0.0/16") + config.ServiceIPRange = serviceIPNet + } + + if config.AdvertisePort == 0 { + config.AdvertisePort = 6445 + } + + if config.ListenPort == 0 { + config.ListenPort = 6444 + } + + if config.DataDir == "" { + config.DataDir = "./management-state" + } +} + +func prepare(config *config.Control, runtime *config.ControlRuntime) error { + var err error + + defaults(config) + + if _, err := os.Stat(config.DataDir); os.IsNotExist(err) { + if err := os.MkdirAll(config.DataDir, 0700); err != nil { + return err + } + } else if err != nil { + return err + } + + config.DataDir, err = filepath.Abs(config.DataDir) + if err != nil { + return err + } + + os.MkdirAll(path.Join(config.DataDir, "tls"), 0700) + os.MkdirAll(path.Join(config.DataDir, "cred"), 0700) + + name := "localhost" + runtime.TLSCert = path.Join(config.DataDir, "tls", name+".crt") + runtime.TLSKey = path.Join(config.DataDir, "tls", name+".key") + runtime.TLSCA = path.Join(config.DataDir, "tls", "ca.crt") + runtime.TLSCAKey = path.Join(config.DataDir, "tls", "ca.key") + runtime.TokenCA = path.Join(config.DataDir, "tls", "token-ca.crt") + runtime.TokenCAKey = path.Join(config.DataDir, "tls", "token-ca.key") + runtime.ServiceKey = path.Join(config.DataDir, "tls", "service.key") + runtime.PasswdFile = path.Join(config.DataDir, "cred", "passwd") + runtime.KubeConfigSystem = path.Join(config.DataDir, "cred", "kubeconfig-system.yaml") + runtime.NodeKey = path.Join(config.DataDir, "tls", "token-node.key") + runtime.NodeCert = path.Join(config.DataDir, "tls", "token-node.crt") + + regen := false + if _, err := os.Stat(runtime.TLSCA); err != nil { + regen = true + if err := genCA(runtime); err != nil { + return err + } + } + + if err := genServiceAccount(runtime); err != nil { + return err + } + + if err := genTLS(regen, config, runtime); err != nil { + return err + } + + if err := genTokenTLS(config, runtime); err != nil { + return err + } + + if err := genUsers(config, runtime); err != nil { + return err + } + + return readTokens(runtime) +} + +func readTokens(runtime *config.ControlRuntime) error { + f, err := os.Open(runtime.PasswdFile) + if err != nil { + return err + } + reader := csv.NewReader(f) + reader.FieldsPerRecord = -1 + + for { + record, err := reader.Read() + if err == io.EOF { + break + } + if err != nil { + return err + } + if len(record) < 2 { + continue + } + + switch record[1] { + case "node": + runtime.NodeToken = "node:" + record[0] + case "admin": + runtime.ClientToken = "admin:" + record[0] + } + } + + return nil +} + +func genUsers(config *config.Control, runtime *config.ControlRuntime) error { + if s, err := os.Stat(runtime.PasswdFile); err == nil && s.Size() > 0 { + return nil + } + + adminToken, err := getToken() + if err != nil { + return err + } + systemToken, err := getToken() + if err != nil { + return err + } + nodeToken, err := getToken() + if err != nil { + return err + } + + passwd := fmt.Sprintf(`%s,admin,admin,system:masters +%s,system,system,system:masters +%s,node,node,system:masters +`, adminToken, systemToken, nodeToken) + + caCertBytes, err := ioutil.ReadFile(runtime.TLSCA) + if err != nil { + return err + } + + caCert := base64.StdEncoding.EncodeToString(caCertBytes) + + if err := kubeConfig(runtime.KubeConfigSystem, fmt.Sprintf("https://localhost:%d", config.ListenPort), caCert, + "system", systemToken); err != nil { + return err + } + + return ioutil.WriteFile(runtime.PasswdFile, []byte(passwd), 0600) +} + +func getToken() (string, error) { + token := make([]byte, 16, 16) + _, err := cryptorand.Read(token) + if err != nil { + return "", err + } + return hex.EncodeToString(token), err +} + +func genTokenTLS(config *config.Control, runtime *config.ControlRuntime) error { + regen := false + if _, err := os.Stat(runtime.TokenCA); err != nil { + regen = true + if err := genTokenCA(runtime); err != nil { + return err + } + } + + _, apiServerServiceIP, err := master.DefaultServiceIPRange(*config.ServiceIPRange) + if err != nil { + return err + } + + cfg := certutil.Config{ + CommonName: "kubernetes", + AltNames: certutil.AltNames{ + DNSNames: []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes", "localhost"}, + IPs: []net.IP{net.ParseIP("127.0.0.1"), apiServerServiceIP}, + }, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + } + + if _, err := os.Stat(runtime.NodeCert); err == nil && !regen { + return nil + } + + caKeyBytes, err := ioutil.ReadFile(runtime.TokenCAKey) + if err != nil { + return err + } + + caBytes, err := ioutil.ReadFile(runtime.TokenCA) + if err != nil { + return err + } + + caKey, err := certutil.ParsePrivateKeyPEM(caKeyBytes) + if err != nil { + return err + } + + caCert, err := certutil.ParseCertsPEM(caBytes) + if err != nil { + return err + } + + key, err := certutil.NewPrivateKey() + if err != nil { + return err + } + + cert, err := certutil.NewSignedCert(cfg, key, caCert[0], caKey.(*rsa.PrivateKey)) + if err != nil { + return err + } + + if err := certutil.WriteKey(runtime.NodeKey, certutil.EncodePrivateKeyPEM(key)); err != nil { + return err + } + + return certutil.WriteCert(runtime.NodeCert, append(certutil.EncodeCertPEM(cert), certutil.EncodeCertPEM(caCert[0])...)) +} + +func genTLS(regen bool, config *config.Control, runtime *config.ControlRuntime) error { + if !regen { + _, certErr := os.Stat(runtime.TLSCert) + _, keyErr := os.Stat(runtime.TLSKey) + if certErr == nil && keyErr == nil { + return nil + } + } + + _, apiServerServiceIP, err := master.DefaultServiceIPRange(*config.ServiceIPRange) + if err != nil { + return err + } + + cfg := certutil.Config{ + CommonName: "localhost", + AltNames: certutil.AltNames{ + DNSNames: []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes", "localhost"}, + IPs: []net.IP{apiServerServiceIP, localhostIP}, + }, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + } + + caKeyBytes, err := ioutil.ReadFile(runtime.TLSCAKey) + if err != nil { + return err + } + + caBytes, err := ioutil.ReadFile(runtime.TLSCA) + if err != nil { + return err + } + + caKey, err := certutil.ParsePrivateKeyPEM(caKeyBytes) + if err != nil { + return err + } + + caCert, err := certutil.ParseCertsPEM(caBytes) + if err != nil { + return err + } + + key, err := certutil.NewPrivateKey() + if err != nil { + return err + } + + cert, err := certutil.NewSignedCert(cfg, key, caCert[0], caKey.(*rsa.PrivateKey)) + if err != nil { + return err + } + + if err := certutil.WriteKey(runtime.TLSKey, certutil.EncodePrivateKeyPEM(key)); err != nil { + return err + } + + return certutil.WriteCert(runtime.TLSCert, append(certutil.EncodeCertPEM(cert), certutil.EncodeCertPEM(caCert[0])...)) +} + +func genServiceAccount(runtime *config.ControlRuntime) error { + _, keyErr := os.Stat(runtime.ServiceKey) + if keyErr == nil { + return nil + } + + key, err := certutil.NewPrivateKey() + if err != nil { + return err + } + + return certutil.WriteKey(runtime.ServiceKey, certutil.EncodePrivateKeyPEM(key)) +} + +func genTokenCA(runtime *config.ControlRuntime) error { + caKey, err := certutil.NewPrivateKey() + if err != nil { + return err + } + + cfg := certutil.Config{ + CommonName: fmt.Sprintf("%s-ca@%d", "k3s-token", time.Now().Unix()), + } + + cert, err := certutil.NewSelfSignedCACert(cfg, caKey) + if err != nil { + return err + } + + if err := certutil.WriteKey(runtime.TokenCAKey, certutil.EncodePrivateKeyPEM(caKey)); err != nil { + return err + } + + return certutil.WriteCert(runtime.TokenCA, certutil.EncodeCertPEM(cert)) +} + +func genCA(runtime *config.ControlRuntime) error { + caKey, err := certutil.NewPrivateKey() + if err != nil { + return err + } + + cfg := certutil.Config{ + CommonName: fmt.Sprintf("%s-ca@%d", "k3s", time.Now().Unix()), + } + + cert, err := certutil.NewSelfSignedCACert(cfg, caKey) + if err != nil { + return err + } + + if err := certutil.WriteKey(runtime.TLSCAKey, certutil.EncodePrivateKeyPEM(caKey)); err != nil { + return err + } + + return certutil.WriteCert(runtime.TLSCA, certutil.EncodeCertPEM(cert)) +} + +func kubeConfig(dest, url, cert, user, password string) error { + data := struct { + URL string + CACert string + User string + Password string + }{ + URL: url, + CACert: cert, + User: user, + Password: password, + } + + output, err := os.Create(dest) + if err != nil { + return err + } + defer output.Close() + + return kubeconfigTemplate.Execute(output, &data) +} diff --git a/pkg/daemons/control/tunnel.go b/pkg/daemons/control/tunnel.go new file mode 100644 index 0000000000..e5020c0f3f --- /dev/null +++ b/pkg/daemons/control/tunnel.go @@ -0,0 +1,50 @@ +package control + +import ( + "context" + "net" + "net/http" + "time" + + "github.com/rancher/norman/pkg/kv" + "github.com/rancher/norman/pkg/remotedialer" + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/kubernetes/cmd/kube-apiserver/app" +) + +func setupTunnel() http.Handler { + tunnelServer := remotedialer.New(authorizer, remotedialer.DefaultErrorWriter) + setupProxyDialer(tunnelServer) + return tunnelServer +} + +func setupProxyDialer(tunnelServer *remotedialer.Server) { + app.DefaultProxyDialerFn = utilnet.DialFunc(func(_ context.Context, network, address string) (net.Conn, error) { + _, port, _ := net.SplitHostPort(address) + addr := "127.0.0.1" + if port != "" { + addr += ":" + port + } + nodeName, _ := kv.Split(address, ":") + return tunnelServer.Dial(nodeName, 15*time.Second, "tcp", addr) + }) +} + +func authorizer(req *http.Request) (clientKey string, authed bool, err error) { + user, ok := request.UserFrom(req.Context()) + if !ok { + return "", false, nil + } + + if user.GetName() != "node" { + return "", false, nil + } + + nodeName := req.Header.Get("X-K3s-NodeName") + if nodeName == "" { + return "", false, nil + } + + return nodeName, true, nil +} diff --git a/pkg/enterchroot/enter.go b/pkg/enterchroot/enter.go new file mode 100644 index 0000000000..301405c097 --- /dev/null +++ b/pkg/enterchroot/enter.go @@ -0,0 +1,254 @@ +package enterchroot + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "time" + + "github.com/docker/docker/pkg/mount" + "github.com/docker/docker/pkg/reexec" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + losetup "gopkg.in/freddierice/go-losetup.v1" +) + +const ( + magic = "_SQMAGIC_" +) + +var ( + symlinks = []string{"lib", "bin", "sbin", "lib64"} +) + +func init() { + reexec.Register("enter-root", enter) +} + +func enter() { + if os.Getenv("ENTER_DEBUG") == "true" { + logrus.SetLevel(logrus.DebugLevel) + } + + logrus.Debug("Running bootstrap") + err := run(os.Getenv("ENTER_DATA")) + if err != nil { + logrus.Fatal(err) + } +} + +func Mount(dataDir string, stdout, stderr io.Writer, args []string) error { + if logrus.GetLevel() >= logrus.DebugLevel { + os.Setenv("ENTER_DEBUG", "true") + } + + root, offset, err := findRoot() + if err != nil { + return err + } + + os.Setenv("ENTER_DATA", dataDir) + os.Setenv("ENTER_ROOT", root) + + logrus.Debugf("Using data [%s] root [%s]", dataDir, root) + + stat, err := os.Stat(root) + if err != nil { + return fmt.Errorf("failed to find %s: %v", root, err) + } + + if !stat.IsDir() { + logrus.Debugf("Attaching file [%s] offset [%d]", root, offset) + dev, err := losetup.Attach(root, offset, true) + if err != nil { + return errors.Wrap(err, "creating loopback device") + } + defer dev.Detach() + os.Setenv("ENTER_DEVICE", dev.Path()) + + go func() { + // Assume that after 3 seconds loop back device has been mounted + time.Sleep(3 * time.Second) + info, err := dev.GetInfo() + if err != nil { + return + } + + info.Flags |= losetup.FlagsAutoClear + err = dev.SetInfo(info) + if err != nil { + return + } + }() + } + + logrus.Debugf("Running enter-root %v", args) + cmd := &exec.Cmd{ + Path: reexec.Self(), + Args: append([]string{"enter-root"}, args...), + SysProcAttr: &syscall.SysProcAttr{ + //Cloneflags: syscall.CLONE_NEWPID | syscall.CLONE_NEWUTS | syscall.CLONE_NEWIPC, + Unshareflags: syscall.CLONE_NEWNS, + Pdeathsig: syscall.SIGKILL, + }, + Stdout: stdout, + Stdin: os.Stdin, + Stderr: stderr, + Env: os.Environ(), + } + return cmd.Run() +} + +func findRoot() (string, uint64, error) { + root := os.Getenv("ENTER_ROOT") + if root != "" { + return root, 0, nil + } + + for _, suffix := range []string{".root", ".squashfs"} { + test := os.Args[0] + suffix + if _, err := os.Stat(test); err == nil { + return test, 0, nil + } + } + + return inFile() +} + +func inFile() (string, uint64, error) { + f, err := os.Open(reexec.Self()) + if err != nil { + return "", 0, err + } + defer f.Close() + + buf := make([]byte, 8192) + test := []byte(strings.ToLower(magic)) + testLength := len(test) + offset := uint64(0) + found := 0 + + for { + n, err := f.Read(buf) + if err == io.EOF && n == 0 { + break + } else if err != nil { + return "", 0, err + } + + for _, b := range buf[:n] { + if b == test[found] { + found++ + if found == testLength { + return reexec.Self(), offset + 1, nil + } + } else { + found = 0 + } + offset++ + } + } + + return "", 0, fmt.Errorf("failed to find image in file %s", os.Args[0]) +} + +func run(data string) error { + os.MkdirAll(data, 0755) + + if err := mount.Mount("tmpfs", data, "tmpfs", ""); err != nil { + return errors.Wrapf(err, "remounting data %s", data) + } + + root := os.Getenv("ENTER_ROOT") + device := os.Getenv("ENTER_DEVICE") + + logrus.Debugf("Using root %s %s", root, device) + + usr := filepath.Join(data, "usr") + dotRoot := filepath.Join(data, ".root") + + for _, d := range []string{usr, dotRoot} { + if err := os.MkdirAll(d, 0755); err != nil { + return fmt.Errorf("failed to make dir %s: %v", data, err) + } + } + + if device == "" { + logrus.Debugf("Bind mounting %s to %s", root, usr) + if err := mount.Mount(root, usr, "none", "bind"); err != nil { + return fmt.Errorf("failed to bind mount") + } + } else { + logrus.Debugf("Mounting squashfs %s to %s", device, usr) + squashErr := checkSquashfs() + if err := mount.Mount(device, usr, "squashfs", "ro"); err != nil { + err = errors.Wrap(err, "mounting squashfs") + if squashErr != nil { + err = errors.Wrap(err, squashErr.Error()) + } + return err + } + } + + if err := os.Chdir(data); err != nil { + return err + } + + for _, p := range symlinks { + if _, err := os.Lstat(p); os.IsNotExist(err) { + if err := os.Symlink(filepath.Join("usr", p), p); err != nil { + return errors.Wrapf(err, "failed to symlink %s", p) + } + } + } + + logrus.Debugf("pivoting to . .root") + if err := syscall.PivotRoot(".", ".root"); err != nil { + return errors.Wrap(err, "pivot_root failed") + } + + if err := mount.ForceMount("", ".", "none", "rprivate"); err != nil { + return errors.Wrapf(err, "making . private %s", data) + } + + if err := syscall.Chroot("/"); err != nil { + return err + } + + if err := os.Chdir("/"); err != nil { + return err + } + + if _, err := os.Stat("/usr/init"); err != nil { + return errors.Wrap(err, "failed to find /usr/init") + } + + return syscall.Exec("/usr/init", os.Args, os.Environ()) +} + +func checkSquashfs() error { + if !inProcFS() { + exec.Command("modprobe", "squashfs").Run() + } + + if !inProcFS() { + return errors.New("This kernel does not support squashfs, please enable. " + + "On Fedora you may need to run \"dnf install kernel-modules-$(uname -r)\"") + } + + return nil +} + +func inProcFS() bool { + bytes, err := ioutil.ReadFile("/proc/filesystems") + if err != nil { + logrus.Errorf("Failed to read /proc/filesystems: %v", err) + return false + } + return strings.Contains(string(bytes), "squashfs") +} diff --git a/pkg/kubectl/main.go b/pkg/kubectl/main.go new file mode 100644 index 0000000000..c73aad1198 --- /dev/null +++ b/pkg/kubectl/main.go @@ -0,0 +1,52 @@ +package kubectl + +import ( + goflag "flag" + "fmt" + "math/rand" + "os" + "time" + + "github.com/docker/docker/pkg/reexec" + "github.com/rancher/rio/pkg/server" + "github.com/spf13/pflag" + utilflag "k8s.io/apiserver/pkg/util/flag" + "k8s.io/apiserver/pkg/util/logs" + "k8s.io/kubernetes/pkg/kubectl/cmd" +) + +func init() { + reexec.Register("kubectl", Main) +} + +func Main() { + kubenv := os.Getenv("KUBECONFIG") + if kubenv == "" { + config, err := server.HomeKubeConfig() + if _, serr := os.Stat(config); err == nil && serr == nil { + os.Setenv("KUBECONFIG", config) + } + } + + main() +} + +func main() { + rand.Seed(time.Now().UnixNano()) + + command := cmd.NewDefaultKubectlCommand() + + // TODO: once we switch everything over to Cobra commands, we can go back to calling + // utilflag.InitFlags() (by removing its pflag.Parse() call). For now, we have to set the + // normalize func and add the go flag set by hand. + pflag.CommandLine.SetNormalizeFunc(utilflag.WordSepNormalizeFunc) + pflag.CommandLine.AddGoFlagSet(goflag.CommandLine) + // utilflag.InitFlags() + logs.InitLogs() + defer logs.FlushLogs() + + if err := command.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} diff --git a/pkg/server/auth.go b/pkg/server/auth.go new file mode 100644 index 0000000000..f0d20aed46 --- /dev/null +++ b/pkg/server/auth.go @@ -0,0 +1,41 @@ +package server + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/rancher/rio/pkg/daemons/config" + "github.com/sirupsen/logrus" + "k8s.io/apiserver/pkg/endpoints/request" +) + +func doAuth(serverConfig *config.Control, next http.Handler, rw http.ResponseWriter, req *http.Request) { + if serverConfig == nil || serverConfig.Runtime.Authenticator == nil { + next.ServeHTTP(rw, req) + return + } + + resp, ok, err := serverConfig.Runtime.Authenticator.AuthenticateRequest(req) + if err != nil { + logrus.Errorf("failed to authenticate request: %v", err) + rw.WriteHeader(http.StatusInternalServerError) + return + } + + if !ok || resp.User.GetName() != "node" { + rw.WriteHeader(http.StatusUnauthorized) + return + } + + ctx := request.WithUser(req.Context(), resp.User) + req = req.WithContext(ctx) + next.ServeHTTP(rw, req) +} + +func authMiddleware(serverConfig *config.Control) mux.MiddlewareFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + doAuth(serverConfig, next, rw, req) + }) + } +} diff --git a/pkg/server/router.go b/pkg/server/router.go new file mode 100644 index 0000000000..83eb9b36b1 --- /dev/null +++ b/pkg/server/router.go @@ -0,0 +1,72 @@ +package server + +import ( + "net/http" + + "github.com/rancher/rio/pkg/daemons/config" + + "k8s.io/apimachinery/pkg/util/json" + + "github.com/gorilla/mux" +) + +type CACertsGetter func() (string, error) + +func router(serverConfig *config.Control, tunnel http.Handler, cacertsGetter CACertsGetter) http.Handler { + authed := mux.NewRouter() + authed.Use(authMiddleware(serverConfig)) + authed.NotFoundHandler = serverConfig.Runtime.Handler + authed.Path("/v1-k3s/connect").Handler(tunnel) + authed.Path("/v1-k3s/node.crt").Handler(nodeCrt(serverConfig)) + authed.Path("/v1-k3s/node.key").Handler(nodeKey(serverConfig)) + authed.Path("/v1-k3s/config").Handler(configHandler(serverConfig)) + + router := mux.NewRouter() + router.NotFoundHandler = authed + router.Path("/cacerts").Handler(cacerts(cacertsGetter)) + + return router +} + +func cacerts(getter CACertsGetter) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + content, err := getter() + if err != nil { + resp.WriteHeader(http.StatusInternalServerError) + resp.Write([]byte(err.Error())) + } + resp.Header().Set("content-type", "text/plain") + resp.Write([]byte(content)) + }) +} + +func nodeCrt(server *config.Control) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if req.TLS == nil { + resp.WriteHeader(http.StatusNotFound) + return + } + http.ServeFile(resp, req, server.Runtime.NodeCert) + }) +} + +func nodeKey(server *config.Control) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if req.TLS == nil { + resp.WriteHeader(http.StatusNotFound) + return + } + http.ServeFile(resp, req, server.Runtime.NodeKey) + }) +} + +func configHandler(server *config.Control) http.Handler { + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + if req.TLS == nil { + resp.WriteHeader(http.StatusNotFound) + return + } + resp.Header().Set("content-type", "application/json") + json.NewEncoder(resp).Encode(server) + }) +} diff --git a/pkg/server/server.go b/pkg/server/server.go new file mode 100644 index 0000000000..8cf2f52dea --- /dev/null +++ b/pkg/server/server.go @@ -0,0 +1,230 @@ +package server + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/rancher/norman" + "github.com/rancher/norman/pkg/clientaccess" + "github.com/rancher/norman/pkg/dynamiclistener" + "github.com/rancher/norman/pkg/resolvehome" + "github.com/rancher/norman/types" + "github.com/rancher/rio/pkg/daemons/config" + "github.com/rancher/rio/pkg/daemons/control" + "github.com/rancher/rio/pkg/tls" + v1 "github.com/rancher/rio/types/apis/k3s.cattle.io/v1" + "github.com/sirupsen/logrus" + "k8s.io/apimachinery/pkg/util/net" +) + +func resolveDataDir(dataDir string) (string, error) { + if dataDir == "" { + if os.Getuid() == 0 { + dataDir = "/var/lib/rancher/k3s" + } else { + dataDir = "${HOME}/.rancher/k3s" + } + } + + dataDir = filepath.Join(dataDir, "server") + return resolvehome.Resolve(dataDir) +} + +func StartServer(ctx context.Context, config *Config) error { + if err := setupDataDirAndChdir(&config.ControlConfig); err != nil { + return err + } + + if err := control.Server(ctx, &config.ControlConfig); err != nil { + return errors.Wrap(err, "starting kubernetes") + } + + certs, err := startNorman(ctx, &config.TLSConfig, &config.ControlConfig) + if err != nil { + return errors.Wrap(err, "starting tls server") + } + + printTokens(certs, config.ControlConfig.NodeConfig.AgentConfig.NodeIP, &config.TLSConfig, &config.ControlConfig) + + writeKubeConfig(certs, &config.TLSConfig, &config.ControlConfig) + + return nil +} + +func startNorman(ctx context.Context, tlsConfig *dynamiclistener.UserConfig, config *config.Control) (string, error) { + var ( + err error + tlsServer dynamiclistener.ServerInterface + ) + + tlsConfig.Handler = router(config, config.Runtime.Tunnel, func() (string, error) { + if tlsServer == nil { + return "", nil + } + return tlsServer.CACert() + }) + + normanConfig := &norman.Config{ + Name: "k3s", + KubeConfig: config.Runtime.KubeConfigSystem, + Clients: []norman.ClientFactory{ + v1.Factory, + }, + Schemas: []*types.Schemas{ + v1.Schemas, + }, + CRDs: map[*types.APIVersion][]string{ + &v1.APIVersion: { + v1.ListenerConfigGroupVersionKind.Kind, + }, + }, + IgnoredKubeConfigEnv: true, + GlobalSetup: func(ctx context.Context) (context.Context, error) { + tlsServer, err = tls.NewServer(ctx, v1.ClientsFrom(ctx).ListenerConfig, *tlsConfig) + return ctx, err + }, + } + + ctx, _, err = normanConfig.Build(ctx, nil) + if err != nil { + return "", err + } + + for { + certs, err := tlsServer.CACert() + if err != nil { + logrus.Infof("waiting to generate CA certs") + time.Sleep(time.Second) + continue + } + return certs, nil + } +} + +func HomeKubeConfig() (string, error) { + return resolvehome.Resolve("${HOME}/.kube/k3s.yaml") +} + +func printTokens(certs, advertiseIP string, tlsConfig *dynamiclistener.UserConfig, config *config.Control) { + var ( + nodeFile string + ) + + if advertiseIP == "" { + advertiseIP = "localhost" + } + + if len(config.Runtime.NodeToken) > 0 { + p := filepath.Join(config.DataDir, "node-token") + if err := writeToken(config.Runtime.NodeToken, p, certs); err == nil { + logrus.Infof("Node token is available at %s", p) + nodeFile = p + } + } + + if len(nodeFile) > 0 { + printToken(tlsConfig.HTTPSPort, advertiseIP, "To join node to cluster:", nodeFile, "agent") + } + +} + +func writeKubeConfig(certs string, tlsConfig *dynamiclistener.UserConfig, config *config.Control) { + clientToken := FormatToken(config.Runtime.ClientToken, certs) + url := fmt.Sprintf("https://localhost:%d", tlsConfig.HTTPSPort) + kubeConfig, err := HomeKubeConfig() + def := true + if err != nil { + kubeConfig = filepath.Join(config.DataDir, "kubeconfig-k3s.yaml") + def = false + } + + if err = clientaccess.AgentAccessInfoToKubeConfig(kubeConfig, url, clientToken); err != nil { + logrus.Errorf("Failed to generate kubeconfig: %v", err) + } + + logrus.Infof("Wrote kubeconfig %s", kubeConfig) + if def { + logrus.Infof("Run: %s kubectl", filepath.Base(os.Args[0])) + } +} + +func setupDataDirAndChdir(config *config.Control) error { + var ( + err error + ) + + config.DataDir, err = resolveDataDir(config.DataDir) + if err != nil { + return err + } + + dataDir := config.DataDir + + if err := os.MkdirAll(dataDir, 0700); err != nil { + return errors.Wrapf(err, "can not mkdir %s", dataDir) + } + + if err := os.Chdir(dataDir); err != nil { + return errors.Wrapf(err, "can not chdir %s", dataDir) + } + + return nil +} + +func readTokenFile(file string) (string, error) { + content, err := ioutil.ReadFile(file) + if err != nil { + return "", err + } + + return strings.TrimSpace(string(content)), nil +} + +func printToken(httpsPort int, advertiseIP, prefix, file, cmd string) { + token, err := readTokenFile(file) + if err != nil { + logrus.Error(err) + } + + ip := advertiseIP + if ip == "" { + hostIP, err := net.ChooseHostInterface() + if err != nil { + logrus.Error(err) + } + ip = hostIP.String() + } + + logrus.Infof("%s rio %s -s https://%s:%d -t %s", prefix, cmd, ip, httpsPort, token) +} + +func FormatToken(token string, certs string) string { + if len(token) == 0 { + return token + } + + prefix := "K10" + if len(certs) > 0 { + digest := sha256.Sum256([]byte(certs)) + prefix = "K10" + hex.EncodeToString(digest[:]) + "::" + } + + return prefix + token +} + +func writeToken(token, file, certs string) error { + if len(token) == 0 { + return nil + } + + token = FormatToken(token, certs) + return ioutil.WriteFile(file, []byte(token+"\n"), 0600) +} diff --git a/pkg/server/types.go b/pkg/server/types.go new file mode 100644 index 0000000000..27216cd2c9 --- /dev/null +++ b/pkg/server/types.go @@ -0,0 +1,12 @@ +package server + +import ( + "github.com/rancher/norman/pkg/dynamiclistener" + "github.com/rancher/rio/pkg/daemons/config" +) + +type Config struct { + DisableAgent bool + TLSConfig dynamiclistener.UserConfig + ControlConfig config.Control +} diff --git a/pkg/tls/storage.go b/pkg/tls/storage.go new file mode 100644 index 0000000000..4afd73acbd --- /dev/null +++ b/pkg/tls/storage.go @@ -0,0 +1,79 @@ +package tls + +import ( + "context" + + "github.com/rancher/norman/pkg/dynamiclistener" + v1 "github.com/rancher/rio/types/apis/k3s.cattle.io/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +const ( + ns = "kube-system" + name = "tls-config" +) + +func NewServer(ctx context.Context, listenerClient v1.ListenerConfigClient, config dynamiclistener.UserConfig) (dynamiclistener.ServerInterface, error) { + storage := &listenerConfigStorage{ + client: listenerClient, + cache: listenerClient.Cache(), + } + + server, err := dynamiclistener.NewServer(storage, config) + listenerClient.OnChange(ctx, "listen-config", func(obj *v1.ListenerConfig) (runtime.Object, error) { + return obj, server.Update(fromStorage(obj)) + }) + + return server, err +} + +type listenerConfigStorage struct { + cache v1.ListenerConfigClientCache + client v1.ListenerConfigClient +} + +func (l *listenerConfigStorage) Set(config *dynamiclistener.ListenerStatus) (*dynamiclistener.ListenerStatus, error) { + if config == nil { + return nil, nil + } + + obj, err := l.cache.Get(ns, name) + if errors.IsNotFound(err) { + ls := v1.NewListenerConfig(ns, name, v1.ListenerConfig{ + Status: *config, + }) + + ls, err := l.client.Create(ls) + return fromStorage(ls), err + } else if err != nil { + return nil, err + } + + obj = obj.DeepCopy() + obj.ResourceVersion = config.Revision + obj.Status = *config + obj.Status.Revision = "" + + obj, err = l.client.Update(obj) + return fromStorage(obj), err +} + +func (l *listenerConfigStorage) Get() (*dynamiclistener.ListenerStatus, error) { + obj, err := l.cache.Get(ns, name) + if errors.IsNotFound(err) { + obj, err = l.client.Get(ns, name, metav1.GetOptions{}) + } + return fromStorage(obj), err +} + +func fromStorage(obj *v1.ListenerConfig) *dynamiclistener.ListenerStatus { + if obj == nil { + return nil + } + + copy := obj.DeepCopy() + copy.Status.Revision = obj.ResourceVersion + return ©.Status +} diff --git a/scripts/build b/scripts/build new file mode 100755 index 0000000000..0773cc1335 --- /dev/null +++ b/scripts/build @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +source $(dirname $0)/version + +cd $(dirname $0)/.. + +LDFLAGS="-X github.com/rancher/rio/version.Version=$VERSION -w -s" +STATIC="-extldflags -static" +STATIC_SQLITE="-extldflags '-static -lm -ldl -lz -lpthread'" + +cross() +{ + echo Building windows CLI + GOOS=windows GOARCH=amd64 go build -ldflags "$LDFLAGS" -o bin/rio-windows ./cli/main.go + echo Building mac CLI + GOOS=darwin GOARCH=amd64 go build -ldflags "$LDFLAGS" -o bin/rio-darwin ./cli/main.go +} + +mkdir -p bin + +if [ -n "$CROSS" ]; then + cross +fi + +echo Building incluster +GOOS=linux GOARCH=amd64 +CGO_ENABLED=0 go build -ldflags "$LDFLAGS $STATIC" -o bin/rio-incluster + +echo Building cli full +CGO_ENABLED=1 go build -tags "static_build libsqlite3 k8s ctr no_etcd netgo osusergo" -ldflags "$LDFLAGS $STATIC_SQLITE" -o bin/rio-full ./cli/main.go diff --git a/scripts/ci b/scripts/ci new file mode 100755 index 0000000000..9b9498e843 --- /dev/null +++ b/scripts/ci @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +cd $(dirname $0) + +./validate +./build +./package +./test diff --git a/scripts/dev-agent.sh b/scripts/dev-agent.sh new file mode 100755 index 0000000000..3543c494f2 --- /dev/null +++ b/scripts/dev-agent.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/../bin + +# Prime sudo +sudo echo Compiling CLI +go build -tags "k8s no_etcd" -o rio-agent ../cli/main.go + +echo Building image and agent +../image/build + +echo Running +exec sudo ENTER_ROOT=../image/main.squashfs ./rio-agent --debug agent -s https://localhost:7443 -t $(<${HOME}/.rancher/rio/server/node-token) diff --git a/scripts/dev-k8s-only-server.sh b/scripts/dev-k8s-only-server.sh new file mode 100755 index 0000000000..255bf477b3 --- /dev/null +++ b/scripts/dev-k8s-only-server.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/../bin + +echo Running +go run -tags "k8s no_etcd" ../cli/main.go --debug server --disable-controllers --disable-agent diff --git a/scripts/dev-login.sh b/scripts/dev-login.sh new file mode 100755 index 0000000000..df29895588 --- /dev/null +++ b/scripts/dev-login.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/../bin + +rio login -s https://localhost:5443 -t $(<${HOME}/.rancher/rio/server/client-token) diff --git a/scripts/dev-server.sh b/scripts/dev-server.sh new file mode 100755 index 0000000000..bf31922961 --- /dev/null +++ b/scripts/dev-server.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/../bin + +echo Running +go run -tags k3s ../cli/main.go --debug server --disable-agent diff --git a/scripts/entry b/scripts/entry new file mode 100755 index 0000000000..78fb567905 --- /dev/null +++ b/scripts/entry @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +mkdir -p bin dist +if [ -e ./scripts/$1 ]; then + ./scripts/"$@" +else + exec "$@" +fi + +chown -R $DAPPER_UID:$DAPPER_GID . diff --git a/scripts/package b/scripts/package new file mode 100755 index 0000000000..86d02160d0 --- /dev/null +++ b/scripts/package @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +cd $(dirname $0) + +./package-cli +./package-image +./package-tar diff --git a/scripts/package-cli b/scripts/package-cli new file mode 100755 index 0000000000..2bbcedb9ad --- /dev/null +++ b/scripts/package-cli @@ -0,0 +1,11 @@ +#!/bin/bash +set -e + +source $(dirname $0)/version + +cd $(dirname $0)/../image +./build + +cp ../bin/rio-full ../bin/rio +echo -n "_sqmagic_" >> ../bin/rio +cat main.squashfs >> ../bin/rio diff --git a/scripts/package-image b/scripts/package-image new file mode 100755 index 0000000000..8fbe222059 --- /dev/null +++ b/scripts/package-image @@ -0,0 +1,17 @@ +#!/bin/bash +set -e + +source $(dirname $0)/version + +cd $(dirname $0)/../package + +TAG=${TAG:-${VERSION}${SUFFIX}} +REPO=${REPO:-rancher} + +cp ../bin/rio ./rio + +IMAGE=${REPO}/rio:${TAG} +docker build -t ${IMAGE} . +mkdir -p ../dist +echo ${IMAGE} > ../dist/images +echo Built ${IMAGE} diff --git a/scripts/package-tar b/scripts/package-tar new file mode 100755 index 0000000000..e59666598f --- /dev/null +++ b/scripts/package-tar @@ -0,0 +1,17 @@ +#!/bin/bash + +cd $(dirname $0)/.. + +. ./scripts/version + +mkdir -p dist/artifacts + +tar cvzf dist/artifacts/rio-${VERSION}-linux-amd64.tar.gz -h bin/rio --xform='s!^bin!rio-'${VERSION}'-linux-amd64!' +tar cvzf dist/artifacts/rio-${VERSION}-darwin.tar.gz bin/rio-darwin --xform='s!.*!rio-'${VERSION}'-darwin/rio!' + +W=rio-${VERSION}-windows +mkdir -p $W +trap "rm -rf $W" EXIT + +cp -f bin/rio-windows $W/rio.exe +zip dist/artifacts/rio-${VERSION}-windows.zip $W/rio.exe diff --git a/scripts/release b/scripts/release new file mode 100755 index 0000000000..7af0df35fc --- /dev/null +++ b/scripts/release @@ -0,0 +1,3 @@ +#!/bin/bash + +exec $(dirname $0)/ci diff --git a/scripts/symlink-k8s.sh b/scripts/symlink-k8s.sh new file mode 100755 index 0000000000..2e65e8435b --- /dev/null +++ b/scripts/symlink-k8s.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +if [ ! -d $1/staging/src/k8s.io ]; then + echo Kubernetes source not found at $1 + exit 1 +fi + +cd $(dirname $0)/../vendor/k8s.io +for i in $1/staging/src/k8s.io/*; do + rm -rf $(basename $i) + ln -s $i . +done +rm -rf kubernetes +mkdir -p kubernetes +cd kubernetes +ln -s $1/{cmd,pkg,plugin,third_party} . diff --git a/scripts/test b/scripts/test new file mode 100755 index 0000000000..f0e1177b7c --- /dev/null +++ b/scripts/test @@ -0,0 +1,50 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/.. + +echo Running tests + +mkdir -p ./build + +mkdir -p /var/lib/rancher/rio/agent +mount -t tmpfs none /var/lib/rancher/rio/agent + +./bin/rio server --disable-agent | grep -v level=info > /var/lib/rancher/rio/agent/agent.log 2>&1 & + +for i in {1..120}; do + if [ ! -e /var/lib/rancher/rio/server/node-token ]; then + sleep .5 + continue + fi + + curl -sf http://localhost:7080/healthz >/dev/null && break + sleep .5 +done +curl -sf http://localhost:7080/healthz >/dev/null + +rm -rf ./image/root +unsquashfs -d ./image/root ./image/main.squashfs +ENTER_ROOT=$(pwd)/image/root ./bin/rio --debug agent -s https://localhost:7443 -t $(>/var/lib/rancher/rio/agent/agent.log 2>&1 & + +export PATH=$(pwd)/bin:$PATH + +rio login -s https://localhost:7443 -t /var/lib/rancher/rio/server/client-token + +echo Waiting for istio/istio-gateway +rio --project=rio-system wait istio/istio-gateway +rio --project=rio-system ps +rio --project=rio-system ps -c + +chmod +x ./tests/init-nfs.bash +./tests/init-nfs.bash +export RUN_NFS_TEST=true + +cd ./tests +tox -- -n $(nproc) +cd .. + +bats -r ./tests || { + tail -n 100 /var/lib/rancher/rio/agent/agent.log + exit 1 +} diff --git a/scripts/validate b/scripts/validate new file mode 100755 index 0000000000..e8549215f3 --- /dev/null +++ b/scripts/validate @@ -0,0 +1,29 @@ +#!/bin/bash +set -e + +cd $(dirname $0)/.. + +echo Running validation + +if grep -r '^[[:space:]]*\[\[.*]][[:space:]]*$' tests; then + echo "Add \"|| false\" to bats tests in any place you use [[ ... ]]" + exit 1 +fi + +PACKAGES="$(go list ./...)" + +echo Running: go vet +go vet ${PACKAGES} +echo Running: gometalinter +for i in ${PACKAGES}; do + if [ -n "$(gometalinter $i | \ + grep -v 'should have comment.*or be unexported' | \ + grep -v 'cli/cmd.*don.t use underscores in Go name' | \ + grep -v 'cli/cmd.*should be DNS' | \ + tee /dev/stderr)" ]; then + failed=true + fi +done +test -z "$failed" +echo Running: go fmt +test -z "$(go fmt ${PACKAGES} | tee /dev/stderr)" diff --git a/scripts/version b/scripts/version new file mode 100755 index 0000000000..87d180fc0c --- /dev/null +++ b/scripts/version @@ -0,0 +1,18 @@ +#!/bin/bash + +if [ -n "$(git status --porcelain --untracked-files=no)" ]; then + DIRTY="-dirty" +fi + +COMMIT=$(git rev-parse --short HEAD) +GIT_TAG=${DRONE_TAG:-$(git tag -l --contains HEAD | head -n 1)} + +if [[ -z "$DIRTY" && -n "$GIT_TAG" ]]; then + VERSION=$GIT_TAG +else + VERSION="${COMMIT}${DIRTY}" +fi + +if [ -z "$ARCH" ]; then + ARCH=amd64 +fi diff --git a/trash.lock b/trash.lock new file mode 100755 index 0000000000..66844e918d --- /dev/null +++ b/trash.lock @@ -0,0 +1,246 @@ +package: package=github.com/rancher/rio +import: +- package: github.com/coreos/flannel + version: 39af3d7e46f2efa156644e247bdcf3b5bc5f1394 +- package: github.com/gorilla/mux + version: v1.6.2 +- package: github.com/gorilla/websocket + version: v1.2.0 +- package: github.com/mattn/go-sqlite3 + version: v1.9.0 +- package: github.com/natefinch/lumberjack + version: aee4629129445bbdfb69aa565537dcfa16544311 +- package: github.com/rancher/norman + version: 5726ebfba191eef161af9638e85bf4fcaa58e008 + repo: https://github.com/ibuildthecloud/norman.git +- package: github.com/urfave/cli + version: 8e01ec4cd3e2d84ab2fe90d8210528ffbb06d8ff +- package: golang.org/x/crypto + version: a49355c7e3f8fe157a85be2f77e6e269a0f89602 +- package: golang.org/x/sync + version: 450f422ab23cf9881c94e2db30cac0eb1b7cf80c +- package: gopkg.in/freddierice/go-losetup.v1 + version: fc9adea44124401d8bfef3a97eaf61b5d44cc2c6 +- package: k8s.io/kubernetes + version: a1d7d1b140f43b6503311ffc1dd80017c553bf8e + repo: file:///home/darren/src/kuberlite/.git + transitive: true + staging: true +- package: github.com/pborman/uuid + version: ca53cad383cad2479bbba7f7a1a05797ec1386e4 +- package: github.com/prometheus/client_model + version: model-0.0.2-12-gfa8ad6fec33561 +- package: github.com/prometheus/common + version: 13ba4ddd0caa9c28ca7b7bffe1dfa9ed8d5ef207 +- package: github.com/golang/protobuf + version: v1.1.0 +- package: golang.org/x/sys + version: 95c6576299259db960f6c5b9b69ea52422860fce +- package: github.com/ugorji/go + version: bdcc60b419d136a85cdf2e7cbcac34b3f1cd6e57 +- package: github.com/vishvananda/netns + version: be1fbeda19366dea804f00efff2dd73a1642fdcc +- package: gopkg.in/natefinch/lumberjack.v2 + version: v1.0-16-g20b71e5b60d756 +- package: vbom.ml/util + version: db5cfe13f5cc80a4990d98e2e1b0707a4d1a5394 +- package: github.com/exponent-io/jsonpath + version: d6023ce2651d8eafb5c75bb0c7167536102ec9f5 +- package: github.com/google/certificate-transparency-go + version: v1.0.21 +- package: github.com/coreos/go-semver + version: v0.2.0-9-ge214231b295a8e +- package: github.com/docker/spdystream + version: 449fdfce4d962303d702fec724ef0ad181c92528 +- package: github.com/renstrom/dedent + version: v1.0.0-3-g020d11c3b9c0c7 +- package: golang.org/x/text + version: b19bf474d317b857955b12035d2c5acb57ce8b01 +- package: github.com/container-storage-interface/spec + version: v1.0.0 +- package: github.com/evanphx/json-patch + version: v4.0.0-3-g36442dbdb58521 +- package: github.com/sigma/go-inotify + version: c87b6cf5033d2c6486046f045eeebdc3d910fd38 +- package: github.com/blang/semver + version: v3.5.0 +- package: github.com/docker/libnetwork + version: v0.8.0-dev.2-1265-ga9cd636e378982 +- package: github.com/fatih/camelcase + version: f6a740d52f961c60348ebb109adde9f4635d7540 +- package: github.com/prometheus/client_golang + version: v0.8.0-83-ge7e903064f5e9e +- package: github.com/shurcooL/sanitized_anchor_name + version: 10ef21a441db47d8b13ebcc5fd2310f636973c77 +- package: github.com/spf13/cobra + version: v0.0.1-34-gc439c4fa093711 +- package: github.com/docker/docker + version: docs-v1.12.0-rc4-2016-07-15-9510-ga9fbbdc8dd8794 +- package: golang.org/x/net + version: 0ed95abb35c445290478a5348a7b38bb154135fd +- package: github.com/json-iterator/go + version: 1.1.4 +- package: github.com/opencontainers/runc + version: v1.0.0-rc5-46-g871ba2e58e2431 +- package: gopkg.in/inf.v0 + version: v0.9.0 +- package: github.com/containerd/containerd + version: v1.0.2 +- package: github.com/ghodss/yaml + version: v1.0.0-4-gc7ce16629ff4cd +- package: github.com/ibuildthecloud/kvsql + version: 6bb3d252056655760ed8ca6557d6d5e607b361d2 +- package: google.golang.org/genproto + version: 09f6ed296fc66555a25fe4ce95173148778dfa85 +- package: github.com/Microsoft/hcsshim + version: v0.6.11 +- package: google.golang.org/grpc + version: v1.13.0 +- package: github.com/gregjones/httpcache + version: 787624de3eb7bd915c329cba748687a3b22666a6 +- package: github.com/golang/groupcache + version: 02826c3e79038b59d737d3b1c0a1d937f71a4433 +- package: github.com/karrick/godirwalk + version: v1.7.5 +- package: github.com/mattn/go-shellwords + version: v1.0.3-20-gf8471b0a71ded0 +- package: github.com/google/gofuzz + version: 44d81051d367757e1c7c6a5a86423ece9afcf63c +- package: github.com/modern-go/concurrent + version: 1.0.3 +- package: github.com/google/cadvisor + version: 25bec0e2ace4846e5caaaf69991046c6f44c4bac + repo: https://github.com/ibuildthecloud/cadvisor.git +- package: sigs.k8s.io/yaml + version: v1.1.0 +- package: bitbucket.org/ww/goautoneg + version: a547fc61f48d567d5b4ec6f8aee5573d8efce11d + repo: https://github.com/rancher/goautoneg.git +- package: github.com/beorn7/perks + version: 3ac7bf7a47d159a033b107610db8a1b6575507a4 +- package: github.com/sirupsen/logrus + version: v1.0.3-11-g89742aefa4b206 +- package: gopkg.in/square/go-jose.v2 + version: v2.1.6-4-g89060dee6a84df +- package: k8s.io/utils + version: 66066c83e385e385ccc3c964b44fd7dcd413d0ed +- package: github.com/daviddengcn/go-colortext + version: 511bcaf42ccd42c38aba7427b6673277bf19e2a1 +- package: github.com/prometheus/procfs + version: 65c1f6f8f0fc1e2185eb9863a3bc751496404259 +- package: golang.org/x/tools + version: 2382e3994d48b1d22acc2c86bcad0a2aff028e32 +- package: github.com/jteeuwen/go-bindata + version: v3.0.7-72-ga0ff2567cfb709 +- package: github.com/modern-go/reflect2 + version: v1.0.1 +- package: golang.org/x/time + version: f51c12702a4d776e4c1fa9b0fabab841babae631 +- package: k8s.io/gengo + version: 51747d6e00da1fc578d5a333a93bb2abcbce7a95 +- package: github.com/euank/go-kmsg-parser + version: v2.0.0 +- package: github.com/chai2010/gettext-go + version: c6fed771bfd517099caf0f7a961671fa8ed08723 +- package: github.com/davecgh/go-spew + version: v1.1.0-1-g782f4967f2dc45 +- package: github.com/docker/distribution + version: v2.6.0-rc.1-209-gedc3ab29cdff86 +- package: github.com/docker/go-units + version: v0.3.1-11-g9e638d38cf6977 +- package: github.com/mindprince/gonvml + version: fee913ce8fb235edf54739d259ca0ecc226c7b8a +- package: github.com/MakeNowJust/heredoc + version: bb23615498cded5e105af4ce27de75b089cbe851 +- package: github.com/syndtr/gocapability + version: e7cb7fa329f456b3855136a2642b197bad7366ba +- package: k8s.io/klog + version: 8139d8cb77af419532b33dfa7dd09fbc5f1d344f +- package: github.com/emicklei/go-restful + version: 2.2.0-4-gff4f55a206334e +- package: github.com/google/btree + version: 7d79101e329e5a3adf994758c578dab82b90c017 +- package: github.com/Azure/go-ansiterm + version: d6e3b3328b783f23731bc4d058875b0371ff8109 +- package: github.com/containerd/console + version: 84eeaae905fa414d03e07bcd6c8d3f19e7cf180e +- package: github.com/pkg/errors + version: v0.8.0 +- package: github.com/googleapis/gnostic + version: 0c5108395e2debce0d731cf0287ddf7242066aba +- package: github.com/imdario/mergo + version: v0.3.5 +- package: github.com/robfig/cron + version: v1-53-gdf38d32658d878 +- package: github.com/JeffAshton/win_pdh + version: 76bb4ee9f0ab50f77826f2a2ee7fb9d3880d6ec2 +- package: github.com/armon/circbuf + version: bbbad097214e2918d8543d5201d12bfd7bca254d +- package: k8s.io/heapster + version: v1.2.0-beta.1 +- package: github.com/matttproud/golang_protobuf_extensions + version: v1.0.1 +- package: github.com/mrunalp/fileutils + version: 4ee1cc9a80582a0c75febdd5cfa779ee4361cbca +- package: github.com/opencontainers/runtime-spec + version: v1.0.0 +- package: github.com/fsnotify/fsnotify + version: v1.3.1-1-gf12c6236fe7b5c +- package: github.com/mistifyio/go-zfs + version: v2.1.1-5-g1b4ae6fb4e77b0 +- package: github.com/russross/blackfriday + version: v1.4-2-g300106c228d52c +- package: github.com/inconshreveable/mousetrap + version: v1.0 +- package: github.com/vishvananda/netlink + version: b2de5d10e38ecce8607e6b438b6d174f389a004e +- package: github.com/mitchellh/go-wordwrap + version: ad45545899c7b13c020ea92b2072220eefad42b8 +- package: github.com/peterbourgon/diskv + version: v2.0.1 +- package: github.com/Microsoft/go-winio + version: v0.4.5 +- package: github.com/cyphar/filepath-securejoin + version: v0.2.1-1-gae69057f2299fb +- package: github.com/Nvveen/Gotty + version: cd527374f1e5bff4938207604a14f2e38a9cf512 +- package: github.com/containernetworking/cni + version: v0.6.0 +- package: github.com/hashicorp/golang-lru + version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 +- package: github.com/jonboulle/clockwork + version: 72f9bd7c4e0c2a40055ab3d0f09654f730cce982 +- package: github.com/seccomp/libseccomp-golang + version: 1b506fc7c24eec5a3693cdcbed40d9c226cfc6a1 +- package: github.com/coreos/pkg + version: v4 +- package: github.com/opencontainers/image-spec + version: v1.0.0-rc6-12-g372ad780f63454 +- package: github.com/opencontainers/selinux + version: v1.0.0-rc1-5-g4a2974bf1ee960 +- package: github.com/cloudflare/cfssl + version: 1.3.2-21-g56268a613adfed +- package: github.com/grpc-ecosystem/go-grpc-prometheus + version: v1.1-4-g2500245aa6110c +- package: github.com/miekg/dns + version: 5d001d020961ae1c184f9f8152fdc73810481677 +- package: github.com/godbus/dbus + version: v3 +- package: github.com/spf13/pflag + version: v1.0.1 +- package: github.com/docker/go-connections + version: v0.3.0 +- package: golang.org/x/oauth2 + version: a6bd8cefa1811bd24b86f8902872e4e8225f74c4 +- package: github.com/gogo/protobuf + version: v0.5 +- package: github.com/mxk/go-flowrate + version: cca7078d478f8520f85629ad7c68962d31ed7682 +- package: github.com/coreos/go-systemd + version: v17 +- package: github.com/opencontainers/go-digest + version: a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb +- package: gopkg.in/yaml.v2 + version: v2.2.1 +- package: github.com/coreos/etcd + version: v3.3.10 diff --git a/types/apis/k3s.cattle.io/v1/schema.go b/types/apis/k3s.cattle.io/v1/schema.go new file mode 100644 index 0000000000..47e1564b8a --- /dev/null +++ b/types/apis/k3s.cattle.io/v1/schema.go @@ -0,0 +1,17 @@ +package v1 + +import ( + "github.com/rancher/norman/types" + "github.com/rancher/norman/types/factory" +) + +var ( + APIVersion = types.APIVersion{ + Version: "v1", + Group: "k3s.cattle.io", + Path: "/v1-k3s", + } + + Schemas = factory.Schemas(&APIVersion). + MustImport(&APIVersion, ListenerConfig{}) +) diff --git a/types/apis/k3s.cattle.io/v1/types.go b/types/apis/k3s.cattle.io/v1/types.go new file mode 100644 index 0000000000..bb7dc4f921 --- /dev/null +++ b/types/apis/k3s.cattle.io/v1/types.go @@ -0,0 +1,16 @@ +package v1 + +import ( + "github.com/rancher/norman/pkg/dynamiclistener" + "github.com/rancher/norman/types" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ListenerConfig struct { + types.Namespaced + + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Status dynamiclistener.ListenerStatus `json:"status,omitempty"` +} diff --git a/types/apis/k3s.cattle.io/v1/zz_generated_deepcopy.go b/types/apis/k3s.cattle.io/v1/zz_generated_deepcopy.go new file mode 100644 index 0000000000..ac63e31185 --- /dev/null +++ b/types/apis/k3s.cattle.io/v1/zz_generated_deepcopy.go @@ -0,0 +1,66 @@ +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ListenerConfig) DeepCopyInto(out *ListenerConfig) { + *out = *in + out.Namespaced = in.Namespaced + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Status.DeepCopyInto(&out.Status) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerConfig. +func (in *ListenerConfig) DeepCopy() *ListenerConfig { + if in == nil { + return nil + } + out := new(ListenerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ListenerConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ListenerConfigList) DeepCopyInto(out *ListenerConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ListenerConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ListenerConfigList. +func (in *ListenerConfigList) DeepCopy() *ListenerConfigList { + if in == nil { + return nil + } + out := new(ListenerConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ListenerConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/types/apis/k3s.cattle.io/v1/zz_generated_k8s_client.go b/types/apis/k3s.cattle.io/v1/zz_generated_k8s_client.go new file mode 100644 index 0000000000..f46b3f301b --- /dev/null +++ b/types/apis/k3s.cattle.io/v1/zz_generated_k8s_client.go @@ -0,0 +1,119 @@ +package v1 + +import ( + "context" + "sync" + + "github.com/rancher/norman/controller" + "github.com/rancher/norman/objectclient" + "github.com/rancher/norman/objectclient/dynamic" + "github.com/rancher/norman/restwatch" + "k8s.io/client-go/rest" +) + +type ( + contextKeyType struct{} + contextClientsKeyType struct{} +) + +type Interface interface { + RESTClient() rest.Interface + controller.Starter + + ListenerConfigsGetter +} + +type Clients struct { + Interface Interface + + ListenerConfig ListenerConfigClient +} + +type Client struct { + sync.Mutex + restClient rest.Interface + starters []controller.Starter + + listenerConfigControllers map[string]ListenerConfigController +} + +func Factory(ctx context.Context, config rest.Config) (context.Context, controller.Starter, error) { + c, err := NewForConfig(config) + if err != nil { + return ctx, nil, err + } + + cs := NewClientsFromInterface(c) + + ctx = context.WithValue(ctx, contextKeyType{}, c) + ctx = context.WithValue(ctx, contextClientsKeyType{}, cs) + return ctx, c, nil +} + +func ClientsFrom(ctx context.Context) *Clients { + return ctx.Value(contextClientsKeyType{}).(*Clients) +} + +func From(ctx context.Context) Interface { + return ctx.Value(contextKeyType{}).(Interface) +} + +func NewClients(config rest.Config) (*Clients, error) { + iface, err := NewForConfig(config) + if err != nil { + return nil, err + } + return NewClientsFromInterface(iface), nil +} + +func NewClientsFromInterface(iface Interface) *Clients { + return &Clients{ + Interface: iface, + + ListenerConfig: &listenerConfigClient2{ + iface: iface.ListenerConfigs(""), + }, + } +} + +func NewForConfig(config rest.Config) (Interface, error) { + if config.NegotiatedSerializer == nil { + config.NegotiatedSerializer = dynamic.NegotiatedSerializer + } + + restClient, err := restwatch.UnversionedRESTClientFor(&config) + if err != nil { + return nil, err + } + + return &Client{ + restClient: restClient, + + listenerConfigControllers: map[string]ListenerConfigController{}, + }, nil +} + +func (c *Client) RESTClient() rest.Interface { + return c.restClient +} + +func (c *Client) Sync(ctx context.Context) error { + return controller.Sync(ctx, c.starters...) +} + +func (c *Client) Start(ctx context.Context, threadiness int) error { + return controller.Start(ctx, threadiness, c.starters...) +} + +type ListenerConfigsGetter interface { + ListenerConfigs(namespace string) ListenerConfigInterface +} + +func (c *Client) ListenerConfigs(namespace string) ListenerConfigInterface { + objectClient := objectclient.NewObjectClient(namespace, c.restClient, &ListenerConfigResource, ListenerConfigGroupVersionKind, listenerConfigFactory{}) + return &listenerConfigClient{ + ns: namespace, + client: c, + objectClient: objectClient, + } +} diff --git a/types/apis/k3s.cattle.io/v1/zz_generated_listener_config_controller.go b/types/apis/k3s.cattle.io/v1/zz_generated_listener_config_controller.go new file mode 100644 index 0000000000..6cb462c416 --- /dev/null +++ b/types/apis/k3s.cattle.io/v1/zz_generated_listener_config_controller.go @@ -0,0 +1,440 @@ +package v1 + +import ( + "context" + + "github.com/rancher/norman/controller" + "github.com/rancher/norman/objectclient" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" +) + +var ( + ListenerConfigGroupVersionKind = schema.GroupVersionKind{ + Version: Version, + Group: GroupName, + Kind: "ListenerConfig", + } + ListenerConfigResource = metav1.APIResource{ + Name: "listenerconfigs", + SingularName: "listenerconfig", + Namespaced: true, + + Kind: ListenerConfigGroupVersionKind.Kind, + } +) + +func NewListenerConfig(namespace, name string, obj ListenerConfig) *ListenerConfig { + obj.APIVersion, obj.Kind = ListenerConfigGroupVersionKind.ToAPIVersionAndKind() + obj.Name = name + obj.Namespace = namespace + return &obj +} + +type ListenerConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ListenerConfig +} + +type ListenerConfigHandlerFunc func(key string, obj *ListenerConfig) (runtime.Object, error) + +type ListenerConfigChangeHandlerFunc func(obj *ListenerConfig) (runtime.Object, error) + +type ListenerConfigLister interface { + List(namespace string, selector labels.Selector) (ret []*ListenerConfig, err error) + Get(namespace, name string) (*ListenerConfig, error) +} + +type ListenerConfigController interface { + Generic() controller.GenericController + Informer() cache.SharedIndexInformer + Lister() ListenerConfigLister + AddHandler(ctx context.Context, name string, handler ListenerConfigHandlerFunc) + AddClusterScopedHandler(ctx context.Context, name, clusterName string, handler ListenerConfigHandlerFunc) + Enqueue(namespace, name string) + Sync(ctx context.Context) error + Start(ctx context.Context, threadiness int) error +} + +type ListenerConfigInterface interface { + ObjectClient() *objectclient.ObjectClient + Create(*ListenerConfig) (*ListenerConfig, error) + GetNamespaced(namespace, name string, opts metav1.GetOptions) (*ListenerConfig, error) + Get(name string, opts metav1.GetOptions) (*ListenerConfig, error) + Update(*ListenerConfig) (*ListenerConfig, error) + Delete(name string, options *metav1.DeleteOptions) error + DeleteNamespaced(namespace, name string, options *metav1.DeleteOptions) error + List(opts metav1.ListOptions) (*ListenerConfigList, error) + Watch(opts metav1.ListOptions) (watch.Interface, error) + DeleteCollection(deleteOpts *metav1.DeleteOptions, listOpts metav1.ListOptions) error + Controller() ListenerConfigController + AddHandler(ctx context.Context, name string, sync ListenerConfigHandlerFunc) + AddLifecycle(ctx context.Context, name string, lifecycle ListenerConfigLifecycle) + AddClusterScopedHandler(ctx context.Context, name, clusterName string, sync ListenerConfigHandlerFunc) + AddClusterScopedLifecycle(ctx context.Context, name, clusterName string, lifecycle ListenerConfigLifecycle) +} + +type listenerConfigLister struct { + controller *listenerConfigController +} + +func (l *listenerConfigLister) List(namespace string, selector labels.Selector) (ret []*ListenerConfig, err error) { + err = cache.ListAllByNamespace(l.controller.Informer().GetIndexer(), namespace, selector, func(obj interface{}) { + ret = append(ret, obj.(*ListenerConfig)) + }) + return +} + +func (l *listenerConfigLister) Get(namespace, name string) (*ListenerConfig, error) { + var key string + if namespace != "" { + key = namespace + "/" + name + } else { + key = name + } + obj, exists, err := l.controller.Informer().GetIndexer().GetByKey(key) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(schema.GroupResource{ + Group: ListenerConfigGroupVersionKind.Group, + Resource: "listenerConfig", + }, key) + } + return obj.(*ListenerConfig), nil +} + +type listenerConfigController struct { + controller.GenericController +} + +func (c *listenerConfigController) Generic() controller.GenericController { + return c.GenericController +} + +func (c *listenerConfigController) Lister() ListenerConfigLister { + return &listenerConfigLister{ + controller: c, + } +} + +func (c *listenerConfigController) AddHandler(ctx context.Context, name string, handler ListenerConfigHandlerFunc) { + c.GenericController.AddHandler(ctx, name, func(key string, obj interface{}) (interface{}, error) { + if obj == nil { + return handler(key, nil) + } else if v, ok := obj.(*ListenerConfig); ok { + return handler(key, v) + } else { + return nil, nil + } + }) +} + +func (c *listenerConfigController) AddClusterScopedHandler(ctx context.Context, name, cluster string, handler ListenerConfigHandlerFunc) { + c.GenericController.AddHandler(ctx, name, func(key string, obj interface{}) (interface{}, error) { + if obj == nil { + return handler(key, nil) + } else if v, ok := obj.(*ListenerConfig); ok && controller.ObjectInCluster(cluster, obj) { + return handler(key, v) + } else { + return nil, nil + } + }) +} + +type listenerConfigFactory struct { +} + +func (c listenerConfigFactory) Object() runtime.Object { + return &ListenerConfig{} +} + +func (c listenerConfigFactory) List() runtime.Object { + return &ListenerConfigList{} +} + +func (s *listenerConfigClient) Controller() ListenerConfigController { + s.client.Lock() + defer s.client.Unlock() + + c, ok := s.client.listenerConfigControllers[s.ns] + if ok { + return c + } + + genericController := controller.NewGenericController(ListenerConfigGroupVersionKind.Kind+"Controller", + s.objectClient) + + c = &listenerConfigController{ + GenericController: genericController, + } + + s.client.listenerConfigControllers[s.ns] = c + s.client.starters = append(s.client.starters, c) + + return c +} + +type listenerConfigClient struct { + client *Client + ns string + objectClient *objectclient.ObjectClient + controller ListenerConfigController +} + +func (s *listenerConfigClient) ObjectClient() *objectclient.ObjectClient { + return s.objectClient +} + +func (s *listenerConfigClient) Create(o *ListenerConfig) (*ListenerConfig, error) { + obj, err := s.objectClient.Create(o) + return obj.(*ListenerConfig), err +} + +func (s *listenerConfigClient) Get(name string, opts metav1.GetOptions) (*ListenerConfig, error) { + obj, err := s.objectClient.Get(name, opts) + return obj.(*ListenerConfig), err +} + +func (s *listenerConfigClient) GetNamespaced(namespace, name string, opts metav1.GetOptions) (*ListenerConfig, error) { + obj, err := s.objectClient.GetNamespaced(namespace, name, opts) + return obj.(*ListenerConfig), err +} + +func (s *listenerConfigClient) Update(o *ListenerConfig) (*ListenerConfig, error) { + obj, err := s.objectClient.Update(o.Name, o) + return obj.(*ListenerConfig), err +} + +func (s *listenerConfigClient) Delete(name string, options *metav1.DeleteOptions) error { + return s.objectClient.Delete(name, options) +} + +func (s *listenerConfigClient) DeleteNamespaced(namespace, name string, options *metav1.DeleteOptions) error { + return s.objectClient.DeleteNamespaced(namespace, name, options) +} + +func (s *listenerConfigClient) List(opts metav1.ListOptions) (*ListenerConfigList, error) { + obj, err := s.objectClient.List(opts) + return obj.(*ListenerConfigList), err +} + +func (s *listenerConfigClient) Watch(opts metav1.ListOptions) (watch.Interface, error) { + return s.objectClient.Watch(opts) +} + +// Patch applies the patch and returns the patched deployment. +func (s *listenerConfigClient) Patch(o *ListenerConfig, patchType types.PatchType, data []byte, subresources ...string) (*ListenerConfig, error) { + obj, err := s.objectClient.Patch(o.Name, o, patchType, data, subresources...) + return obj.(*ListenerConfig), err +} + +func (s *listenerConfigClient) DeleteCollection(deleteOpts *metav1.DeleteOptions, listOpts metav1.ListOptions) error { + return s.objectClient.DeleteCollection(deleteOpts, listOpts) +} + +func (s *listenerConfigClient) AddHandler(ctx context.Context, name string, sync ListenerConfigHandlerFunc) { + s.Controller().AddHandler(ctx, name, sync) +} + +func (s *listenerConfigClient) AddLifecycle(ctx context.Context, name string, lifecycle ListenerConfigLifecycle) { + sync := NewListenerConfigLifecycleAdapter(name, false, s, lifecycle) + s.Controller().AddHandler(ctx, name, sync) +} + +func (s *listenerConfigClient) AddClusterScopedHandler(ctx context.Context, name, clusterName string, sync ListenerConfigHandlerFunc) { + s.Controller().AddClusterScopedHandler(ctx, name, clusterName, sync) +} + +func (s *listenerConfigClient) AddClusterScopedLifecycle(ctx context.Context, name, clusterName string, lifecycle ListenerConfigLifecycle) { + sync := NewListenerConfigLifecycleAdapter(name+"_"+clusterName, true, s, lifecycle) + s.Controller().AddClusterScopedHandler(ctx, name, clusterName, sync) +} + +type ListenerConfigIndexer func(obj *ListenerConfig) ([]string, error) + +type ListenerConfigClientCache interface { + Get(namespace, name string) (*ListenerConfig, error) + List(namespace string, selector labels.Selector) ([]*ListenerConfig, error) + + Index(name string, indexer ListenerConfigIndexer) + GetIndexed(name, key string) ([]*ListenerConfig, error) +} + +type ListenerConfigClient interface { + Create(*ListenerConfig) (*ListenerConfig, error) + Get(namespace, name string, opts metav1.GetOptions) (*ListenerConfig, error) + Update(*ListenerConfig) (*ListenerConfig, error) + Delete(namespace, name string, options *metav1.DeleteOptions) error + List(namespace string, opts metav1.ListOptions) (*ListenerConfigList, error) + Watch(opts metav1.ListOptions) (watch.Interface, error) + + Cache() ListenerConfigClientCache + + OnCreate(ctx context.Context, name string, sync ListenerConfigChangeHandlerFunc) + OnChange(ctx context.Context, name string, sync ListenerConfigChangeHandlerFunc) + OnRemove(ctx context.Context, name string, sync ListenerConfigChangeHandlerFunc) + Enqueue(namespace, name string) + + Generic() controller.GenericController + ObjectClient() *objectclient.ObjectClient + Interface() ListenerConfigInterface +} + +type listenerConfigClientCache struct { + client *listenerConfigClient2 +} + +type listenerConfigClient2 struct { + iface ListenerConfigInterface + controller ListenerConfigController +} + +func (n *listenerConfigClient2) Interface() ListenerConfigInterface { + return n.iface +} + +func (n *listenerConfigClient2) Generic() controller.GenericController { + return n.iface.Controller().Generic() +} + +func (n *listenerConfigClient2) ObjectClient() *objectclient.ObjectClient { + return n.Interface().ObjectClient() +} + +func (n *listenerConfigClient2) Enqueue(namespace, name string) { + n.iface.Controller().Enqueue(namespace, name) +} + +func (n *listenerConfigClient2) Create(obj *ListenerConfig) (*ListenerConfig, error) { + return n.iface.Create(obj) +} + +func (n *listenerConfigClient2) Get(namespace, name string, opts metav1.GetOptions) (*ListenerConfig, error) { + return n.iface.GetNamespaced(namespace, name, opts) +} + +func (n *listenerConfigClient2) Update(obj *ListenerConfig) (*ListenerConfig, error) { + return n.iface.Update(obj) +} + +func (n *listenerConfigClient2) Delete(namespace, name string, options *metav1.DeleteOptions) error { + return n.iface.DeleteNamespaced(namespace, name, options) +} + +func (n *listenerConfigClient2) List(namespace string, opts metav1.ListOptions) (*ListenerConfigList, error) { + return n.iface.List(opts) +} + +func (n *listenerConfigClient2) Watch(opts metav1.ListOptions) (watch.Interface, error) { + return n.iface.Watch(opts) +} + +func (n *listenerConfigClientCache) Get(namespace, name string) (*ListenerConfig, error) { + return n.client.controller.Lister().Get(namespace, name) +} + +func (n *listenerConfigClientCache) List(namespace string, selector labels.Selector) ([]*ListenerConfig, error) { + return n.client.controller.Lister().List(namespace, selector) +} + +func (n *listenerConfigClient2) Cache() ListenerConfigClientCache { + n.loadController() + return &listenerConfigClientCache{ + client: n, + } +} + +func (n *listenerConfigClient2) OnCreate(ctx context.Context, name string, sync ListenerConfigChangeHandlerFunc) { + n.loadController() + n.iface.AddLifecycle(ctx, name+"-create", &listenerConfigLifecycleDelegate{create: sync}) +} + +func (n *listenerConfigClient2) OnChange(ctx context.Context, name string, sync ListenerConfigChangeHandlerFunc) { + n.loadController() + n.iface.AddLifecycle(ctx, name+"-change", &listenerConfigLifecycleDelegate{update: sync}) +} + +func (n *listenerConfigClient2) OnRemove(ctx context.Context, name string, sync ListenerConfigChangeHandlerFunc) { + n.loadController() + n.iface.AddLifecycle(ctx, name, &listenerConfigLifecycleDelegate{remove: sync}) +} + +func (n *listenerConfigClientCache) Index(name string, indexer ListenerConfigIndexer) { + err := n.client.controller.Informer().GetIndexer().AddIndexers(map[string]cache.IndexFunc{ + name: func(obj interface{}) ([]string, error) { + if v, ok := obj.(*ListenerConfig); ok { + return indexer(v) + } + return nil, nil + }, + }) + + if err != nil { + panic(err) + } +} + +func (n *listenerConfigClientCache) GetIndexed(name, key string) ([]*ListenerConfig, error) { + var result []*ListenerConfig + objs, err := n.client.controller.Informer().GetIndexer().ByIndex(name, key) + if err != nil { + return nil, err + } + for _, obj := range objs { + if v, ok := obj.(*ListenerConfig); ok { + result = append(result, v) + } + } + + return result, nil +} + +func (n *listenerConfigClient2) loadController() { + if n.controller == nil { + n.controller = n.iface.Controller() + } +} + +type listenerConfigLifecycleDelegate struct { + create ListenerConfigChangeHandlerFunc + update ListenerConfigChangeHandlerFunc + remove ListenerConfigChangeHandlerFunc +} + +func (n *listenerConfigLifecycleDelegate) HasCreate() bool { + return n.create != nil +} + +func (n *listenerConfigLifecycleDelegate) Create(obj *ListenerConfig) (runtime.Object, error) { + if n.create == nil { + return obj, nil + } + return n.create(obj) +} + +func (n *listenerConfigLifecycleDelegate) HasFinalize() bool { + return n.remove != nil +} + +func (n *listenerConfigLifecycleDelegate) Remove(obj *ListenerConfig) (runtime.Object, error) { + if n.remove == nil { + return obj, nil + } + return n.remove(obj) +} + +func (n *listenerConfigLifecycleDelegate) Updated(obj *ListenerConfig) (runtime.Object, error) { + if n.update == nil { + return obj, nil + } + return n.update(obj) +} diff --git a/types/apis/k3s.cattle.io/v1/zz_generated_listener_config_lifecycle_adapter.go b/types/apis/k3s.cattle.io/v1/zz_generated_listener_config_lifecycle_adapter.go new file mode 100644 index 0000000000..058e82268d --- /dev/null +++ b/types/apis/k3s.cattle.io/v1/zz_generated_listener_config_lifecycle_adapter.go @@ -0,0 +1,62 @@ +package v1 + +import ( + "github.com/rancher/norman/lifecycle" + "k8s.io/apimachinery/pkg/runtime" +) + +type ListenerConfigLifecycle interface { + Create(obj *ListenerConfig) (runtime.Object, error) + Remove(obj *ListenerConfig) (runtime.Object, error) + Updated(obj *ListenerConfig) (runtime.Object, error) +} + +type listenerConfigLifecycleAdapter struct { + lifecycle ListenerConfigLifecycle +} + +func (w *listenerConfigLifecycleAdapter) HasCreate() bool { + o, ok := w.lifecycle.(lifecycle.ObjectLifecycleCondition) + return !ok || o.HasCreate() +} + +func (w *listenerConfigLifecycleAdapter) HasFinalize() bool { + o, ok := w.lifecycle.(lifecycle.ObjectLifecycleCondition) + return !ok || o.HasFinalize() +} + +func (w *listenerConfigLifecycleAdapter) Create(obj runtime.Object) (runtime.Object, error) { + o, err := w.lifecycle.Create(obj.(*ListenerConfig)) + if o == nil { + return nil, err + } + return o, err +} + +func (w *listenerConfigLifecycleAdapter) Finalize(obj runtime.Object) (runtime.Object, error) { + o, err := w.lifecycle.Remove(obj.(*ListenerConfig)) + if o == nil { + return nil, err + } + return o, err +} + +func (w *listenerConfigLifecycleAdapter) Updated(obj runtime.Object) (runtime.Object, error) { + o, err := w.lifecycle.Updated(obj.(*ListenerConfig)) + if o == nil { + return nil, err + } + return o, err +} + +func NewListenerConfigLifecycleAdapter(name string, clusterScoped bool, client ListenerConfigInterface, l ListenerConfigLifecycle) ListenerConfigHandlerFunc { + adapter := &listenerConfigLifecycleAdapter{lifecycle: l} + syncFn := lifecycle.NewObjectLifecycleAdapter(name, clusterScoped, adapter, client.ObjectClient()) + return func(key string, obj *ListenerConfig) (runtime.Object, error) { + newObj, err := syncFn(key, obj) + if o, ok := newObj.(runtime.Object); ok { + return o, err + } + return nil, err + } +} diff --git a/types/apis/k3s.cattle.io/v1/zz_generated_scheme.go b/types/apis/k3s.cattle.io/v1/zz_generated_scheme.go new file mode 100644 index 0000000000..9744026d89 --- /dev/null +++ b/types/apis/k3s.cattle.io/v1/zz_generated_scheme.go @@ -0,0 +1,40 @@ +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +const ( + GroupName = "k3s.cattle.io" + Version = "v1" +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: Version} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + // TODO this gets cleaned up when the types are fixed + scheme.AddKnownTypes(SchemeGroupVersion, + + &ListenerConfig{}, + &ListenerConfigList{}, + ) + return nil +} diff --git a/types/codegen/cleanup/main.go b/types/codegen/cleanup/main.go new file mode 100644 index 0000000000..7fb7d83868 --- /dev/null +++ b/types/codegen/cleanup/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/rancher/norman/generator/cleanup" + "github.com/sirupsen/logrus" +) + +func main() { + if err := cleanup.Cleanup("./types"); err != nil { + logrus.Fatal(err) + } +} diff --git a/types/codegen/main.go b/types/codegen/main.go new file mode 100644 index 0000000000..30ef66aa92 --- /dev/null +++ b/types/codegen/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "github.com/rancher/norman/generator" + v1 "github.com/rancher/rio/types/apis/k3s.cattle.io/v1" + "github.com/sirupsen/logrus" +) + +var ( + basePackage = "github.com/rancher/rio/types" +) + +func main() { + if err := generator.DefaultGenerate(v1.Schemas, basePackage, false, nil); err != nil { + logrus.Fatal(err) + } +} diff --git a/vendor.conf b/vendor.conf new file mode 100644 index 0000000000..45c4a70c67 --- /dev/null +++ b/vendor.conf @@ -0,0 +1,17 @@ +package=github.com/rancher/rio +package=github.com/jteeuwen/go-bindata +package=github.com/jteeuwen/go-bindata/go-bindata + +k8s.io/kubernetes a1d7d1b140f43b6503311ffc1dd80017c553bf8e file:///home/darren/src/kuberlite/.git transitive=true,staging=true + +github.com/rancher/norman 5726ebfba191eef161af9638e85bf4fcaa58e008 https://github.com/ibuildthecloud/norman.git +github.com/coreos/flannel 39af3d7e46f2efa156644e247bdcf3b5bc5f1394 +github.com/natefinch/lumberjack aee4629129445bbdfb69aa565537dcfa16544311 +github.com/gorilla/mux v1.6.2 +github.com/gorilla/websocket v1.2.0 +github.com/mattn/go-sqlite3 v1.9.0 +github.com/patrickmn/go-cache v2.1.0 +golang.org/x/crypto a49355c7e3f8fe157a85be2f77e6e269a0f89602 +gopkg.in/freddierice/go-losetup.v1 fc9adea44124401d8bfef3a97eaf61b5d44cc2c6 +github.com/urfave/cli 8e01ec4cd3e2d84ab2fe90d8210528ffbb06d8ff +golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000000..a645beef9e --- /dev/null +++ b/version/version.go @@ -0,0 +1,6 @@ +package version + +var ( + Version = "dev" + GitCommit = "HEAD" +)