From e2e7c3570f4e715b4b1539ea425e4eee8800841f Mon Sep 17 00:00:00 2001 From: yue9944882 <291271447@qq.com> Date: Thu, 22 Oct 2020 16:52:02 +0800 Subject: [PATCH] rebasing apiserver-runtime pt2 --- .github/workflows/build.yml | 3 +- Makefile | 4 +- test/Makefile.test => Makefile.test | 2 +- VERSION | 1 - cmd/apiserver-boot/BUILD.bazel | 1 - cmd/apiserver-boot/boot/create/group.go | 5 +- cmd/apiserver-boot/boot/create/resource.go | 49 ++++++++----- cmd/apiserver-boot/boot/create/subresource.go | 2 +- cmd/apiserver-boot/boot/create/util.go | 2 +- cmd/apiserver-boot/boot/create/version.go | 6 +- cmd/apiserver-boot/boot/init_repo/repo.go | 23 +++++-- cmd/apiserver-boot/boot/util/BUILD.bazel | 3 + cmd/apiserver-boot/boot/util/repo.go | 69 +++++++++++++++++++ cmd/apiserver-boot/boot/util/util.go | 3 - cmd/apiserver-boot/main.go | 25 +------ go.mod | 1 + scripts/install_etcd.sh | 16 ----- scripts/print-workspace-status.sh | 3 +- scripts/workspace-install.sh | 13 ---- 19 files changed, 129 insertions(+), 102 deletions(-) rename test/Makefile.test => Makefile.test (97%) delete mode 100644 VERSION create mode 100644 cmd/apiserver-boot/boot/util/repo.go delete mode 100755 scripts/install_etcd.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b1e8dc6565..d4b80032b7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,11 +37,10 @@ jobs: GOPATH: ${{ github.workspace }}/go run: make install - name: Testing on new project - working-directory: ${{ github.workspace }}/go/src/sigs.k8s.io/apiserver-builder-alpha/test env: - GOPATH: ${{ github.workspace }}/go GOBIN: ${{ github.workspace }}/go/bin run: | + mkdir -p test; cd test; cp Makefile.test test/ export PATH=${PATH}:${GOBIN} make test -f Makefile.test basic-example-build: diff --git a/Makefile b/Makefile index 6330f20f2d..c63de9d635 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,7 @@ gazelle: NAME=apiserver-builder-alpha VENDOR=kubernetes-sigs -VERSION=$(shell cat VERSION) -COMMIT=$(shell git rev-parse --verify HEAD) +VERSION=$(shell git rev-parse --verify --short HEAD) DESCRIPTION=apiserver-builder implements libraries and tools to quickly and easily build Kubernetes apiservers to support custom resource types. MAINTAINER=The Kubernetes Authors URL=https://github.com/$(VENDOR)/$(NAME) @@ -53,7 +52,6 @@ clean: .PHONY: build build: clean ## Create release artefacts for darwin:amd64, linux:amd64 and windows:amd64. Requires etcd, glide, hg. - go mod vendor mkdir -p release/$(VERSION)/src bazel build --platforms=@io_bazel_rules_go//go/toolchain:$(GOOS)_$(GOARCH) cmd:apiserver-builder ls -lh bazel-bin/cmd diff --git a/test/Makefile.test b/Makefile.test similarity index 97% rename from test/Makefile.test rename to Makefile.test index d8c91ad869..90d6615825 100644 --- a/test/Makefile.test +++ b/Makefile.test @@ -16,7 +16,7 @@ all: test -NON_INTERACTIVE_FLAG=--skip-resource=false --skip-controller=false --skip-admission-controller=false +NON_INTERACTIVE_FLAG=--skip-resource=false --skip-controller=false --with-status-subresource=true test: cmds skeleton check go test ./pkg/... diff --git a/VERSION b/VERSION deleted file mode 100644 index f72019128e..0000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -v1.18.0 diff --git a/cmd/apiserver-boot/BUILD.bazel b/cmd/apiserver-boot/BUILD.bazel index 49c1042cfc..024f5fbf4b 100644 --- a/cmd/apiserver-boot/BUILD.bazel +++ b/cmd/apiserver-boot/BUILD.bazel @@ -10,7 +10,6 @@ go_library( "//cmd/apiserver-boot/boot/create:go_default_library", "//cmd/apiserver-boot/boot/init_repo:go_default_library", "//cmd/apiserver-boot/boot/run:go_default_library", - "//cmd/apiserver-boot/boot/util:go_default_library", "//cmd/apiserver-boot/boot/version:go_default_library", "@com_github_spf13_cobra//:go_default_library", "@io_k8s_klog//:go_default_library", diff --git a/cmd/apiserver-boot/boot/create/group.go b/cmd/apiserver-boot/boot/create/group.go index f20e137da4..e0dcf47fd7 100644 --- a/cmd/apiserver-boot/boot/create/group.go +++ b/cmd/apiserver-boot/boot/create/group.go @@ -90,9 +90,6 @@ var groupTemplate = ` {{.BoilerPlate}} -//go:generate deepcopy-gen -O zz_generated.deepcopy -i . -h ../../../boilerplate.go.txt -//go:generate defaulter-gen -O zz_generated.defaults -i . -h ../../../boilerplate.go.txt - // +k8s:deepcopy-gen=package,register // +groupName={{.Name}}.{{.Domain}} @@ -105,4 +102,4 @@ var installTemplate = ` {{.BoilerPlate}} package {{.Name}} -` \ No newline at end of file +` diff --git a/cmd/apiserver-boot/boot/create/resource.go b/cmd/apiserver-boot/boot/create/resource.go index 9a8750c5ed..7eeec21a19 100644 --- a/cmd/apiserver-boot/boot/create/resource.go +++ b/cmd/apiserver-boot/boot/create/resource.go @@ -39,6 +39,7 @@ var nonNamespacedKind bool var skipGenerateAdmissionController bool var skipGenerateResource bool var skipGenerateController bool +var withStatusSubresource bool var createResourceCmd = &cobra.Command{ Use: "resource", @@ -59,6 +60,8 @@ func AddCreateResource(cmd *cobra.Command) { createResourceCmd.Flags().BoolVar(&skipGenerateResource, "skip-resource", false, "if set, the resources will not be generated") createResourceCmd.Flags().BoolVar(&skipGenerateController, "skip-controller", false, "if set, the controller will not be generated") createResourceCmd.Flags().BoolVar(&skipGenerateAdmissionController, "skip-admission-controller", false, "if set, the admission controller will not be generated") + createResourceCmd.Flags().MarkDeprecated("skip-admission-controller", "") + createResourceCmd.Flags().BoolVar(&withStatusSubresource, "with-status-subresource", true, "if set, the status sub-resource will be generated") cmd.AddCommand(createResourceCmd) } @@ -114,10 +117,10 @@ func createResource(boilerplate string) { kindName, resourceName, shortName, - util.Repo, + util.GetRepo(), inflect.NewDefaultRuleset().Pluralize(kindName), nonNamespacedKind, - false, + withStatusSubresource, } found := false @@ -145,7 +148,7 @@ func createResource(boilerplate string) { scaffoldRegister = "// +kubebuilder:scaffold:resource-register" ) mainFile := filepath.Join("cmd", "apiserver", "main.go") - newImport := fmt.Sprintf(`%s%s "%s/pkg/apis/%s/%s"`, groupName, versionName, util.Repo, groupName, versionName) + newImport := fmt.Sprintf(`%s%s "%s/pkg/apis/%s/%s"`, groupName, versionName, util.GetRepo(), groupName, versionName) if err := appendMixin(mainFile, scaffoldImports, newImport); err != nil { klog.Fatal(err) } @@ -211,14 +214,14 @@ func createResource(boilerplate string) { Version: versionName, Kind: kindName, Plural: resourceName, - Package: filepath.Join(util.Repo, "pkg", "apis", groupName, versionName), + Package: filepath.Join(util.GetRepo(), "pkg", "apis", groupName, versionName), ImportAlias: resourceName, } scaffolder := scaffolds.NewAPIScaffolder( &config.Config{ MultiGroup: true, Domain: util.Domain, - Repo: util.Repo, + Repo: util.GetRepo(), Version: config.Version3Alpha, }, boilerplate, // TODO @@ -240,17 +243,16 @@ func createResource(boilerplate string) { } type resourceTemplateArgs struct { - BoilerPlate string - Domain string - Group string - Version string - Kind string - Resource string - ShortName string - Repo string - PluralizedKind string - NonNamespacedKind bool - + BoilerPlate string + Domain string + Group string + Version string + Kind string + Resource string + ShortName string + Repo string + PluralizedKind string + NonNamespacedKind bool WithStatusSubResource bool } @@ -340,9 +342,6 @@ type {{.Kind}}Status struct { var _ resource.Object = &{{.Kind}}{} var _ resource.ObjectList = &{{.Kind}}List{} var _ resourcestrategy.Validater = &{{.Kind}}{} -{{- if .WithStatusSubResource }} -var _ resource.ObjectWithStatusSubResource = &{{.Kind}}{} -{{- end }} func (in *{{.Kind}}) GetObjectMeta() *metav1.ObjectMeta { @@ -380,6 +379,18 @@ func (in *{{.Kind}}) Validate(ctx context.Context) field.ErrorList { func (in *{{.Kind}}List) GetListMeta() *metav1.ListMeta { return &in.ListMeta } + +{{- if .WithStatusSubResource }} +var _ resource.ObjectWithStatusSubResource = &{{.Kind}}{} + +func (in *{{.Kind}}) SetStatus(statusSubResource interface{}) { + in.Status = statusSubResource.({{.Kind}}Status) +} + +func (in *{{.Kind}}) GetStatus() (statusSubResource interface{}) { + return in.Status +} +{{- end }} ` var resourceSuiteTestTemplate = ` diff --git a/cmd/apiserver-boot/boot/create/subresource.go b/cmd/apiserver-boot/boot/create/subresource.go index a95dcd37c6..1ad9f6b213 100644 --- a/cmd/apiserver-boot/boot/create/subresource.go +++ b/cmd/apiserver-boot/boot/create/subresource.go @@ -84,7 +84,7 @@ func createSubresource(boilerplate string) { boilerplate, subresourceName, strings.Title(kindName) + strings.Title(subresourceName), - util.Repo, + util.GetRepo(), groupName, versionName, kindName, diff --git a/cmd/apiserver-boot/boot/create/util.go b/cmd/apiserver-boot/boot/create/util.go index 88cc7b5b9e..964c71a38f 100644 --- a/cmd/apiserver-boot/boot/create/util.go +++ b/cmd/apiserver-boot/boot/create/util.go @@ -24,8 +24,8 @@ import ( "regexp" "strings" - "github.com/pkg/errors" "github.com/markbates/inflect" + "github.com/pkg/errors" "github.com/spf13/cobra" utilvalidation "k8s.io/apimachinery/pkg/util/validation" diff --git a/cmd/apiserver-boot/boot/create/version.go b/cmd/apiserver-boot/boot/create/version.go index b630fa0f62..b3cd469eb6 100644 --- a/cmd/apiserver-boot/boot/create/version.go +++ b/cmd/apiserver-boot/boot/create/version.go @@ -87,7 +87,7 @@ func createVersion(boilerplate string) { util.Domain, groupName, versionName, - util.Repo, + util.GetRepo(), }) path = filepath.Join(dir, "pkg", "apis", groupName, versionName, "register.go") @@ -118,10 +118,6 @@ var versionTemplate = ` // backward compatibility by support multiple concurrent versions // of the same resource -//go:generate deepcopy-gen -O zz_generated.deepcopy -i . -h ../../../../boilerplate.go.txt -//go:generate defaulter-gen -O zz_generated.defaults -i . -h ../../../../boilerplate.go.txt -//go:generate conversion-gen -O zz_generated.conversion -i . -h ../../../../boilerplate.go.txt - // +k8s:openapi-gen=true // +k8s:deepcopy-gen=package,register // +k8s:conversion-gen={{.Repo}}/pkg/apis/{{.Group}} diff --git a/cmd/apiserver-boot/boot/init_repo/repo.go b/cmd/apiserver-boot/boot/init_repo/repo.go index 874d5e490e..9bf44c1699 100644 --- a/cmd/apiserver-boot/boot/init_repo/repo.go +++ b/cmd/apiserver-boot/boot/init_repo/repo.go @@ -36,6 +36,7 @@ var repoCmd = &cobra.Command{ var domain string var copyright string +var moduleName string func AddInitRepo(cmd *cobra.Command) { cmd.AddCommand(repoCmd) @@ -43,6 +44,8 @@ func AddInitRepo(cmd *cobra.Command) { // Hide this flag by default repoCmd.Flags().StringVar(©right, "copyright", "boilerplate.go.txt", "Location of copyright boilerplate file.") + repoCmd.Flags().StringVar(&moduleName, "module-name", "", + "the module name of the go mod project, required if the project uses go module outside GOPATH") } func RunInitRepo(cmd *cobra.Command, args []string) { @@ -51,6 +54,14 @@ func RunInitRepo(cmd *cobra.Command, args []string) { } cr := util.GetCopyright(copyright) + if len(moduleName) == 0 { + if err := util.LoadRepoFromGoPath(); err != nil { + klog.Fatal(err) + } + } else { + util.SetRepo(moduleName) + } + createControllerManager(cr) createGoMod() createKubeBuilderProjectFile() @@ -79,7 +90,7 @@ func createKubeBuilderProjectFile() { } path := filepath.Join(dir, "PROJECT") util.WriteIfNotFound(path, "project-template", projectFileTemplate, - buildTemplateArguments{domain, util.Repo}) + buildTemplateArguments{domain, util.GetRepo()}) } var projectFileTemplate = ` @@ -97,7 +108,7 @@ func createBazelWorkspace() { util.WriteIfNotFound(path, "bazel-workspace-template", workspaceTemplate, nil) path = filepath.Join(dir, "BUILD.bazel") util.WriteIfNotFound(path, "bazel-build-template", - buildTemplate, buildTemplateArguments{domain, util.Repo}) + buildTemplate, buildTemplateArguments{domain, util.GetRepo()}) } func createControllerManager(boilerplate string) { @@ -105,7 +116,7 @@ func createControllerManager(boilerplate string) { &config.Config{ MultiGroup: true, Domain: util.Domain, - Repo: util.Repo, + Repo: util.GetRepo(), Version: config.Version3Alpha, }, "", @@ -157,7 +168,7 @@ func createApiserver(boilerplate string) { apiserverTemplateArguments{ domain, boilerplate, - util.Repo, + util.GetRepo(), }) } @@ -212,7 +223,7 @@ func createGoMod() { path := filepath.Join(dir, "go.mod") util.Overwrite(path, "gomod-template", goModTemplate, goModTemplateArguments{ - util.Repo, + util.GetRepo(), }) } @@ -255,7 +266,7 @@ load("@io_k8s_repo_infra//:repos.bzl", "configure") # use k8s.io/repo-infra to configure go and bazel # default minimum_bazel_version is 0.29.1 configure( - go_version = "1.13", + go_version = "1.15", rbe_name = None, ) ` diff --git a/cmd/apiserver-boot/boot/util/BUILD.bazel b/cmd/apiserver-boot/boot/util/BUILD.bazel index a10807968e..39ebb847da 100644 --- a/cmd/apiserver-boot/boot/util/BUILD.bazel +++ b/cmd/apiserver-boot/boot/util/BUILD.bazel @@ -3,6 +3,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ + "repo.go", "untar.go", "util.go", "x509.go", @@ -11,7 +12,9 @@ go_library( visibility = ["//visibility:public"], deps = [ "@com_github_markbates_inflect//:go_default_library", + "@com_github_pkg_errors//:go_default_library", "@io_k8s_apiserver//pkg/server:go_default_library", "@io_k8s_klog//:go_default_library", + "@org_golang_x_mod//modfile:go_default_library", ], ) diff --git a/cmd/apiserver-boot/boot/util/repo.go b/cmd/apiserver-boot/boot/util/repo.go new file mode 100644 index 0000000000..96f4cba087 --- /dev/null +++ b/cmd/apiserver-boot/boot/util/repo.go @@ -0,0 +1,69 @@ +package util + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "golang.org/x/mod/modfile" + "k8s.io/klog" +) + +var repo string + +func LoadRepoFromGoPath() error { + gopath := os.Getenv("GOPATH") + if len(gopath) == 0 { + return fmt.Errorf("GOPATH not defined") + } + goSrc := filepath.Join(gopath, "src") + wd, err := os.Getwd() + if err != nil { + return err + } + if !strings.HasPrefix(filepath.Dir(wd), goSrc) { + return fmt.Errorf("apiserver-boot must be run from the directory containing the go package to "+ + "bootstrap. This must be under $GOPATH/src/. "+ + "\nCurrent GOPATH=%s. \nCurrent directory=%s", gopath, wd) + } + repo = strings.Replace(wd, goSrc+string(filepath.Separator), "", 1) + return nil +} + +func LoadRepoFromGoMod() error { + mod, err := ioutil.ReadFile("go.mod") + if err != nil { + return errors.Wrap(err, "failed reading go.mod file") + } + modPath := modfile.ModulePath(mod) + if len(modPath) == 0 { + return fmt.Errorf("failed parsing go.mod, empty module path") + } + repo = modPath + return nil +} + +func LoadRepoFromGoPathOrGoMod() error { + if err := LoadRepoFromGoPath(); err != nil { + // reading from go mod + return LoadRepoFromGoMod() + } + return nil +} + +func GetRepo() string { + if len(repo) > 0 { + return repo + } + if err := LoadRepoFromGoPathOrGoMod(); err != nil { + klog.Fatal(err) + } + return repo +} + +func SetRepo(r string) { + repo = r +} diff --git a/cmd/apiserver-boot/boot/util/util.go b/cmd/apiserver-boot/boot/util/util.go index 38680b5782..de2618d4bb 100644 --- a/cmd/apiserver-boot/boot/util/util.go +++ b/cmd/apiserver-boot/boot/util/util.go @@ -33,8 +33,6 @@ import ( ) var Domain string -var Repo string -var GoSrc string // writeIfNotFound returns true if the file was created and false if it already exists func WriteIfNotFound(path, templateName, templateValue string, data interface{}) bool { @@ -100,7 +98,6 @@ func Overwrite(path, templateName, templateValue string, data interface{}) bool return true } - func GetCopyright(file string) string { if len(file) == 0 { file = "boilerplate.go.txt" diff --git a/cmd/apiserver-boot/main.go b/cmd/apiserver-boot/main.go index 4e4b182ce6..f96dec444e 100644 --- a/cmd/apiserver-boot/main.go +++ b/cmd/apiserver-boot/main.go @@ -17,39 +17,16 @@ limitations under the License. package main import ( - "os" - "path/filepath" - "strings" - "github.com/spf13/cobra" - "k8s.io/klog" "sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot/boot/build" "sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot/boot/create" "sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot/boot/init_repo" "sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot/boot/run" - "sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot/boot/util" "sigs.k8s.io/apiserver-builder-alpha/cmd/apiserver-boot/boot/version" ) func main() { - gopath := os.Getenv("GOPATH") - if len(gopath) == 0 { - klog.Fatal("GOPATH not defined") - } - util.GoSrc = filepath.Join(gopath, "src") - - wd, err := os.Getwd() - if err != nil { - klog.Fatal(err) - } - - if !strings.HasPrefix(filepath.Dir(wd), util.GoSrc) { - klog.Fatalf("apiserver-boot must be run from the directory containing the go package to "+ - "bootstrap. This must be under $GOPATH/src/. "+ - "\nCurrent GOPATH=%s. \nCurrent directory=%s", gopath, wd) - } - util.Repo = strings.Replace(wd, util.GoSrc+string(filepath.Separator), "", 1) init_repo.AddInit(cmd) create.AddCreate(cmd) @@ -66,7 +43,7 @@ var cmd = &cobra.Command{ Use: "apiserver-boot", Short: "apiserver-boot development kit for building Kubernetes extensions in go.", Long: `apiserver-boot development kit for building Kubernetes extensions in go.`, - Example: `# Initialize your repository with scaffolding directories and go files. + Example: `# Initialize your repository with scaffolding directories and go files. Specify --module-name if the project works outside GOPATH. apiserver-boot init repo --domain example.com # Create new resource "Bee" in the "insect" group with version "v1beta1" diff --git a/go.mod b/go.mod index 26499a4cf9..88cdb08aa7 100644 --- a/go.mod +++ b/go.mod @@ -24,4 +24,5 @@ require ( k8s.io/klog v1.0.0 k8s.io/utils v0.0.0-20200912215256-4140de9c8800 // indirect sigs.k8s.io/kubebuilder v1.0.9-0.20200925141511-a2f239880b04 + golang.org/x/mod v0.3.0 ) diff --git a/scripts/install_etcd.sh b/scripts/install_etcd.sh deleted file mode 100755 index 4d0d39682a..0000000000 --- a/scripts/install_etcd.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env bash - -# Copied from https://github.com/coreos/etcd/releases instructions - -ETCD_VER=v3.2.0 - -# choose either URL -GOOGLE_URL=https://storage.googleapis.com/etcd -GITHUB_URL=https://github.com/coreos/etcd/releases/download -DOWNLOAD_URL=${GOOGLE_URL} - -rm -f /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -rm -rf /tmp/test-etcd && mkdir -p /tmp/test-etcd - -curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz -C /tmp/test-etcd --strip-components=1 diff --git a/scripts/print-workspace-status.sh b/scripts/print-workspace-status.sh index 10962e06f0..a73495bb67 100755 --- a/scripts/print-workspace-status.sh +++ b/scripts/print-workspace-status.sh @@ -5,9 +5,8 @@ set -o nounset set -o pipefail SCRIPT_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. -VERSION_FILE=${SCRIPT_ROOT}/VERSION -apiserver_builder_version=$(cat ${VERSION_FILE}) +apiserver_builder_version=$(git rev-parse --verify --short HEAD) k8s_vendor=kubernetes-1.18.4 git_commit="$(git rev-parse HEAD)" build_date="$(date +%Y-%m-%d-%H:%M:%S)" diff --git a/scripts/workspace-install.sh b/scripts/workspace-install.sh index a5539ee08b..adb68e3339 100644 --- a/scripts/workspace-install.sh +++ b/scripts/workspace-install.sh @@ -1,22 +1,9 @@ #!/usr/bin/env bash [[ -f bazel_0.29.1-linux-x86_64.deb ]] || wget https://github.com/bazelbuild/bazel/releases/download/0.29.1/bazel_0.29.1-linux-x86_64.deb -[[ -f kubernetes-server-linux-amd64.tar.gz ]] || wget https://dl.k8s.io/v1.16.0/kubernetes-server-linux-amd64.tar.gz -[[ -f etcd-v3.2.0-linux-amd64.tar.gz ]] || wget https://github.com/coreos/etcd/releases/download/v3.2.0/etcd-v3.2.0-linux-amd64.tar.gz # bazel installation sudo dpkg -i bazel_0.29.1-linux-x86_64.deb -# kubebuilder installation -sudo mkdir -p /usr/local/kubebuilder/bin/ -tar -zxvf kubernetes-server-linux-amd64.tar.gz -sudo mv kubernetes/server/bin/kube-apiserver /usr/local/kubebuilder/bin/ - -# etcd installation -mkdir -p /tmp/test-etcd/ -tar xzvf etcd-v3.2.0-linux-amd64.tar.gz -C /tmp/test-etcd/ --strip-components=1 -sudo cp /tmp/test-etcd/{etcd,etcdctl} /usr/local/kubebuilder/bin/ -sudo cp /tmp/test-etcd/{etcd,etcdctl} /usr/local/bin/ - # install apiserver-boots mkdir -p $(go env GOPATH)/bin/