diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9042cda7e..c8f9ba941 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,13 +4,12 @@ on: push: branches: - 'master' - - 'dev' + - 'release*' tags: - - 'release-*' + - 'v*' pull_request: branches: - 'master' - - 'dev' jobs: build: @@ -39,7 +38,7 @@ jobs: run: bash hack/install_kubebuilder.sh - name: Build - run: make all + run: make test - name: Make OpenAPI Spec run: make openapi diff --git a/Makefile b/Makefile index 950bf7b88..eb42f30ef 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ define ALL_HELP_INFO # debugging tools like delve. endef .PHONY: all -all: test hypersphere ks-apiserver controller-manager +all: test ks-apiserver controller-manager # Build ks-apiserver binary ks-apiserver: fmt vet @@ -44,10 +44,6 @@ ks-apiserver: fmt vet controller-manager: fmt vet hack/gobuild.sh cmd/controller-manager -# Build hypersphere binary -hypersphere: fmt vet - hack/gobuild.sh cmd/hypersphere - # Run go fmt against code fmt: generate gofmt -w ./pkg ./cmd ./tools ./api diff --git a/build/ks-apiserver/Dockerfile b/build/ks-apiserver/Dockerfile index 551c6cdac..989818f78 100644 --- a/build/ks-apiserver/Dockerfile +++ b/build/ks-apiserver/Dockerfile @@ -1,23 +1,7 @@ # Copyright 2018 The KubeSphere Authors. All rights reserved. # Use of this source code is governed by an Apache license # that can be found in the LICENSE file. - -FROM golang:1.12 as ks-apiserver-builder - -COPY / /go/src/kubesphere.io/kubesphere - -WORKDIR /go/src/kubesphere.io/kubesphere -RUN GIT_VERSION=$(git describe --always --dirty) && \ - GIT_HASH=$(git rev-parse HEAD) && \ - BUILDDATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') && \ - CGO_ENABLED=0 GO111MODULE=on GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor \ - go build -i -ldflags \ - '-w -s -X kubesphere.io/kubesphere/pkg/version.version=$(GIT_VERSION) \ - -X kubesphere.io/kubesphere/pkg/version.gitCommit=$(GIT_HASH) \ - -X kubesphere.io/kubesphere/pkg/version.buildDate=$(BUILDDATE)' \ - -o ks-apiserver cmd/ks-apiserver/apiserver.go - FROM alpine:3.9 RUN apk add --update ca-certificates && update-ca-certificates -COPY --from=ks-apiserver-builder /go/src/kubesphere.io/kubesphere/ks-apiserver /usr/local/bin/ +COPY /bin/cmd/ks-apiserver /usr/local/bin/ CMD ["sh"] diff --git a/build/ks-controller-manager/Dockerfile b/build/ks-controller-manager/Dockerfile index 0b9bc8615..c5362343b 100644 --- a/build/ks-controller-manager/Dockerfile +++ b/build/ks-controller-manager/Dockerfile @@ -1,15 +1,7 @@ # Copyright 2018 The KubeSphere Authors. All rights reserved. # Use of this source code is governed by an Apache license # that can be found in the LICENSE file. - -FROM golang:1.12 as controller-manager-builder - -COPY / /go/src/kubesphere.io/kubesphere -WORKDIR /go/src/kubesphere.io/kubesphere - -RUN CGO_ENABLED=0 GO111MODULE=on GOOS=linux GOARCH=amd64 GOFLAGS=-mod=vendor go build --ldflags "-extldflags -static" -o controller-manager ./cmd/controller-manager/ - FROM alpine:3.7 RUN apk add --update ca-certificates && update-ca-certificates -COPY --from=controller-manager-builder /go/src/kubesphere.io/kubesphere/controller-manager /usr/local/bin/ +COPY /bin/cmd/controller-manager /usr/local/bin/ CMD controller-manager diff --git a/cmd/controller-manager/app/server.go b/cmd/controller-manager/app/server.go index 449dd0d58..b5d706dad 100644 --- a/cmd/controller-manager/app/server.go +++ b/cmd/controller-manager/app/server.go @@ -82,6 +82,7 @@ func NewControllerManagerCommand() *cobra.Command { os.Exit(1) } }, + SilenceUsage: true, } fs := cmd.Flags() @@ -111,24 +112,21 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) if s.DevopsOptions != nil && len(s.DevopsOptions.Host) != 0 { devopsClient, err = jenkins.NewDevopsClient(s.DevopsOptions) if err != nil { - klog.Errorf("Failed to create devops client %v", err) - return err + return fmt.Errorf("failed to connect jenkins, please check jenkins status, error: %v", err) } } var ldapClient ldapclient.Interface if s.LdapOptions == nil || len(s.LdapOptions.Host) == 0 { - klog.Errorf("Failed to create devops client invalid ldap options") - return err - } - - if s.LdapOptions.Host == ldapclient.FAKE_HOST { - ldapClient = ldapclient.NewSimpleLdap() + return fmt.Errorf("ldap service address MUST not be empty") } else { - ldapClient, err = ldapclient.NewLdapClient(s.LdapOptions, stopCh) - if err != nil { - klog.Errorf("Failed to create ldap client %v", err) - return err + if s.LdapOptions.Host == ldapclient.FAKE_HOST { // for debug only + ldapClient = ldapclient.NewSimpleLdap() + } else { + ldapClient, err = ldapclient.NewLdapClient(s.LdapOptions, stopCh) + if err != nil { + return fmt.Errorf("failed to connect to ldap service, please check ldap status, error: %v", err) + } } } @@ -136,8 +134,7 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) if s.OpenPitrixOptions != nil && !s.OpenPitrixOptions.IsEmpty() { openpitrixClient, err = openpitrix.NewClient(s.OpenPitrixOptions) if err != nil { - klog.Errorf("Failed to create openpitrix client %v", err) - return err + return fmt.Errorf("failed to connect to openpitrix, please check openpitrix status, error: %v", err) } } @@ -145,13 +142,17 @@ func Run(s *options.KubeSphereControllerManagerOptions, stopCh <-chan struct{}) if s.S3Options != nil && len(s.S3Options.Endpoint) != 0 { s3Client, err = s3.NewS3Client(s.S3Options) if err != nil { - klog.Errorf("Failed to create s3 client %v", err) - return err + return fmt.Errorf("failed to connect to s3, please check s3 service status, error: %v", err) } } - informerFactory := informers.NewInformerFactories(kubernetesClient.Kubernetes(), kubernetesClient.KubeSphere(), - kubernetesClient.Istio(), kubernetesClient.Application(), kubernetesClient.Snapshot(), kubernetesClient.ApiExtensions()) + informerFactory := informers.NewInformerFactories( + kubernetesClient.Kubernetes(), + kubernetesClient.KubeSphere(), + kubernetesClient.Istio(), + kubernetesClient.Application(), + kubernetesClient.Snapshot(), + kubernetesClient.ApiExtensions()) run := func(ctx context.Context) { klog.V(0).Info("setting up manager") diff --git a/cmd/ks-apiserver/app/options/options.go b/cmd/ks-apiserver/app/options/options.go index c453c57ac..a26780a8c 100644 --- a/cmd/ks-apiserver/app/options/options.go +++ b/cmd/ks-apiserver/app/options/options.go @@ -92,10 +92,12 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS kubernetesClient.Istio(), kubernetesClient.Application(), kubernetesClient.Snapshot(), kubernetesClient.ApiExtensions()) apiServer.InformerFactory = informerFactory - if s.MonitoringOptions.Endpoint != "" { + if s.MonitoringOptions == nil || len(s.MonitoringOptions.Endpoint) == 0 { + return nil, fmt.Errorf("moinitoring service address in configuration MUST not be empty, please check configmap/kubesphere-config in kubesphere-system namespace") + } else { monitoringClient, err := prometheus.NewPrometheus(s.MonitoringOptions) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to connect to prometheus, please check prometheus status, error: %v", err) } apiServer.MonitoringClient = monitoringClient } @@ -103,7 +105,7 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS if s.LoggingOptions.Host != "" { loggingClient, err := esclient.NewElasticsearch(s.LoggingOptions) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to connect to elasticsearch, please check elasticsearch status, error: %v", err) } apiServer.LoggingClient = loggingClient } @@ -114,7 +116,7 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS } else { s3Client, err := s3.NewS3Client(s.S3Options) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to connect to s3, please check s3 service status, error: %v", err) } apiServer.S3Client = s3Client } @@ -123,7 +125,7 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS if s.DevopsOptions.Host != "" { devopsClient, err := jenkins.NewDevopsClient(s.DevopsOptions) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to connect to jenkins, please check jenkins status, error: %v", err) } apiServer.DevopsClient = devopsClient } @@ -131,19 +133,21 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS if s.SonarQubeOptions.Host != "" { sonarClient, err := sonarqube.NewSonarQubeClient(s.SonarQubeOptions) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to connecto to sonarqube, please check sonarqube status, error: %v", err) } apiServer.SonarClient = sonarqube.NewSonar(sonarClient.SonarQube()) } var cacheClient cache.Interface - if s.RedisOptions.Host != "" { + if s.RedisOptions == nil || len(s.RedisOptions.Host) == 0 { + return nil, fmt.Errorf("redis service address MUST not be empty, please check configmap/kubesphere-config in kubesphere-system namespace") + } else { if s.RedisOptions.Host == fakeInterface && s.DebugMode { apiServer.CacheClient = cache.NewSimpleCache() } else { cacheClient, err = cache.NewRedisClient(s.RedisOptions, stopCh) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to connect to redis service, please check redis status, error: %v", err) } apiServer.CacheClient = cacheClient } @@ -152,7 +156,7 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS if s.EventsOptions.Host != "" { eventsClient, err := eventsclient.NewClient(s.EventsOptions) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to connect to elasticsearch, please check elasticsearch status, error: %v", err) } apiServer.EventsClient = eventsClient } @@ -160,7 +164,7 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS if s.AuditingOptions.Host != "" { auditingClient, err := auditingclient.NewClient(s.AuditingOptions) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to connect to elasticsearch, please check elasticsearch status, error: %v", err) } apiServer.AuditingClient = auditingClient } @@ -168,7 +172,7 @@ func (s *ServerRunOptions) NewAPIServer(stopCh <-chan struct{}) (*apiserver.APIS if s.OpenPitrixOptions != nil && !s.OpenPitrixOptions.IsEmpty() { opClient, err := openpitrix.NewClient(s.OpenPitrixOptions) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to connect to openpitrix, please check openpitrix status, error: %v", err) } apiServer.OpenpitrixClient = opClient } diff --git a/cmd/ks-apiserver/app/server.go b/cmd/ks-apiserver/app/server.go index 0cbc44671..43de5fab3 100644 --- a/cmd/ks-apiserver/app/server.go +++ b/cmd/ks-apiserver/app/server.go @@ -54,6 +54,7 @@ cluster's shared state through which all other components interact.`, return Run(s, signals.SetupSignalHandler()) }, + SilenceUsage: true, } fs := cmd.Flags() diff --git a/hack/docker_build.sh b/hack/docker_build.sh index 00e488ebd..42c483f78 100755 --- a/hack/docker_build.sh +++ b/hack/docker_build.sh @@ -3,17 +3,17 @@ set -ex set -o pipefail +tag_for_branch() { + local tag=$1 + if [[ "${tag}" == "master" ]]; then + tag="latest" + fi + echo ${tag} +} + # push to kubespheredev with default latest tag REPO=${REPO:-kubespheredev} -TAG=${TRAVIS_BRANCH:-latest} - -# check if build was triggered by a travis cronjob -if [[ -z "$TRAVIS_EVENT_TYPE" ]]; then - echo "TRAVIS_EVENT_TYPE is empty, also normaly build" -elif [[ $TRAVIS_EVENT_TYPE == "cron" ]]; then - TAG=dev-$(date +%Y%m%d) -fi - +TAG=$(tag_for_branch $1) docker build -f build/ks-apiserver/Dockerfile -t $REPO/ks-apiserver:$TAG . docker build -f build/ks-controller-manager/Dockerfile -t $REPO/ks-controller-manager:$TAG . diff --git a/hack/gobuild.sh b/hack/gobuild.sh index d0a37bfe7..8b848f9a7 100755 --- a/hack/gobuild.sh +++ b/hack/gobuild.sh @@ -20,6 +20,9 @@ set -o errexit set -o nounset set -o pipefail +KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +source "${KUBE_ROOT}/hack/lib/init.sh" + VERBOSE=${VERBOSE:-"0"} V="" if [[ "${VERBOSE}" == "1" ]];then @@ -33,13 +36,13 @@ OUTPUT_DIR=bin BUILDPATH=./${1:?"path to build"} OUT=${OUTPUT_DIR}/${1:?"output path"} -set -e - BUILD_GOOS=${GOOS:-linux} BUILD_GOARCH=${GOARCH:-amd64} GOBINARY=${GOBINARY:-go} +LDFLAGS=$(kube::version::ldflags) # forgoing -i (incremental build) because it will be deprecated by tool chain. time GOOS=${BUILD_GOOS} CGO_ENABLED=0 GOARCH=${BUILD_GOARCH} ${GOBINARY} build \ + -ldflags="${LDFLAGS}" \ -o ${OUT} \ ${BUILDPATH} diff --git a/hack/install_kubebuilder.sh b/hack/install_kubebuilder.sh index e4cfe50db..b3ec57554 100755 --- a/hack/install_kubebuilder.sh +++ b/hack/install_kubebuilder.sh @@ -10,7 +10,7 @@ # Check if the program is installed, otherwise exit function command_exists () { - if ! [ -x "$(command -v $1)" ]; then + if ! [[ -x "$(command -v $1)" ]]; then echo "Error: $1 program is not installed." >&2 exit 1 fi @@ -45,14 +45,14 @@ esac command_exists curl command_exists tar -KUBEBUILDER_VERSION=v1.0.8 +KUBEBUILDER_VERSION=v2.3.1 KUBEBUILDER_VERSION=${KUBEBUILDER_VERSION#"v"} KUBEBUILDER_VERSION_NAME="kubebuilder_${KUBEBUILDER_VERSION}" KUBEBUILDER_DIR=/usr/local/kubebuilder # Check if folder containing kubebuilder executable exists and is not empty -if [ -d "$KUBEBUILDER_DIR" ]; then - if [ "$(ls -A $KUBEBUILDER_DIR)" ]; then +if [[ -d "$KUBEBUILDER_DIR" ]]; then + if [[ "$(ls -A ${KUBEBUILDER_DIR})" ]]; then echo "\n/usr/local/kubebuilder folder is not empty. Please delete or backup it before to install ${KUBEBUILDER_VERSION_NAME}" exit 1 fi @@ -64,7 +64,7 @@ pushd $TMP_DIR # Downloading Kubebuilder compressed file using curl program URL="https://github.com/kubernetes-sigs/kubebuilder/releases/download/v${KUBEBUILDER_VERSION}/${KUBEBUILDER_VERSION_NAME}_${OSEXT}_${ARCH}.tar.gz" echo "Downloading ${KUBEBUILDER_VERSION_NAME}\nfrom $URL\n" -curl -L "$URL"| tar xz -C $TMP_DIR +curl -L "$URL"| tar xz -C ${TMP_DIR} echo "Downloaded executable files" ls "${KUBEBUILDER_VERSION_NAME}_${OSEXT}_${ARCH}/bin" @@ -75,6 +75,6 @@ mv ${KUBEBUILDER_VERSION_NAME}_${OSEXT}_${ARCH} kubebuilder && sudo mv -f kubebu echo "Add kubebuilder to your path; e.g copy paste in your shell and/or edit your ~/.profile file" echo "export PATH=\$PATH:/usr/local/kubebuilder/bin" popd -rm -rf $TMP_DIR +rm -rf ${TMP_DIR} export PATH=$PATH:/usr/local/kubebuilder/bin diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh index f2deeb47c..a89908f13 100755 --- a/hack/lib/golang.sh +++ b/hack/lib/golang.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # This is a modified version of Kubernetes - +KUBE_GO_PACKAGE=kubesphere.io/kubesphere # Ensure the go tool exists and is a viable version. kube::golang::verify_go_version() { @@ -26,3 +26,39 @@ EOF return 2 fi } + +# Prints the value that needs to be passed to the -ldflags parameter of go build +# in order to set the Kubernetes based on the git tree status. +# IMPORTANT: if you update any of these, also update the lists in +# pkg/version/def.bzl and hack/print-workspace-status.sh. +kube::version::ldflags() { + kube::version::get_version_vars + + local -a ldflags + function add_ldflag() { + local key=${1} + local val=${2} + # If you update these, also update the list component-base/version/def.bzl. + ldflags+=( + "-X '${KUBE_GO_PACKAGE}/pkg/version.${key}=${val}'" + ) + } + + add_ldflag "buildDate" "$(date ${SOURCE_DATE_EPOCH:+"--date=@${SOURCE_DATE_EPOCH}"} -u +'%Y-%m-%dT%H:%M:%SZ')" + if [[ -n ${KUBE_GIT_COMMIT-} ]]; then + add_ldflag "gitCommit" "${KUBE_GIT_COMMIT}" + add_ldflag "gitTreeState" "${KUBE_GIT_TREE_STATE}" + fi + + if [[ -n ${KUBE_GIT_VERSION-} ]]; then + add_ldflag "gitVersion" "${KUBE_GIT_VERSION}" + fi + + if [[ -n ${KUBE_GIT_MAJOR-} && -n ${KUBE_GIT_MINOR-} ]]; then + add_ldflag "gitMajor" "${KUBE_GIT_MAJOR}" + add_ldflag "gitMinor" "${KUBE_GIT_MINOR}" + fi + + # The -ldflags parameter takes a single string, so join the output. + echo "${ldflags[*]-}" +} \ No newline at end of file diff --git a/hack/lib/init.sh b/hack/lib/init.sh index e9fe7a44a..56bdce33a 100755 --- a/hack/lib/init.sh +++ b/hack/lib/init.sh @@ -19,6 +19,8 @@ export THIS_PLATFORM_BIN="${KUBE_ROOT}/_output/bin" source "${KUBE_ROOT}/hack/lib/util.sh" source "${KUBE_ROOT}/hack/lib/logging.sh" +source "${KUBE_ROOT}/hack/lib/version.sh" + kube::log::install_errexit diff --git a/hack/lib/version.sh b/hack/lib/version.sh new file mode 100644 index 000000000..0c341b80e --- /dev/null +++ b/hack/lib/version.sh @@ -0,0 +1,98 @@ +#!/usr/bin/env bash + +# ----------------------------------------------------------------------------- +# Version management helpers. These functions help to set, save and load the +# following variables: +# +# KUBE_GIT_COMMIT - The git commit id corresponding to this +# source code. +# KUBE_GIT_TREE_STATE - "clean" indicates no changes since the git commit id +# "dirty" indicates source code changes after the git commit id +# "archive" indicates the tree was produced by 'git archive' +# KUBE_GIT_VERSION - "vX.Y" used to indicate the last release version. +# KUBE_GIT_MAJOR - The major part of the version +# KUBE_GIT_MINOR - The minor component of the version + +# Grovels through git to set a set of env variables. +# +# If KUBE_GIT_VERSION_FILE, this function will load from that file instead of +# querying git. +kube::version::get_version_vars() { + # If the kubernetes source was exported through git archive, then + # we likely don't have a git tree, but these magic values may be filled in. + # shellcheck disable=SC2016,SC2050 + # Disabled as we're not expanding these at runtime, but rather expecting + # that another tool may have expanded these and rewritten the source (!) + if [[ '$Format:%%$' == "%" ]]; then + KUBE_GIT_COMMIT='$Format:%H$' + KUBE_GIT_TREE_STATE="archive" + # When a 'git archive' is exported, the '$Format:%D$' below will look + # something like 'HEAD -> release-1.8, tag: v1.8.3' where then 'tag: ' + # can be extracted from it. + if [[ '$Format:%D$' =~ tag:\ (v[^ ,]+) ]]; then + KUBE_GIT_VERSION="${BASH_REMATCH[1]}" + fi + fi + + local git=(git --work-tree "${KUBE_ROOT}") + + if [[ -n ${KUBE_GIT_COMMIT-} ]] || KUBE_GIT_COMMIT=$("${git[@]}" rev-parse "HEAD^{commit}" 2>/dev/null); then + if [[ -z ${KUBE_GIT_TREE_STATE-} ]]; then + # Check if the tree is dirty. default to dirty + if git_status=$("${git[@]}" status --porcelain 2>/dev/null) && [[ -z ${git_status} ]]; then + KUBE_GIT_TREE_STATE="clean" + else + KUBE_GIT_TREE_STATE="dirty" + fi + fi + + # Use git describe to find the version based on tags. + if [[ -n ${KUBE_GIT_VERSION-} ]] || KUBE_GIT_VERSION=$("${git[@]}" describe --tags --match='v*' --abbrev=14 "${KUBE_GIT_COMMIT}^{commit}" 2>/dev/null); then + # This translates the "git describe" to an actual semver.org + # compatible semantic version that looks something like this: + # v1.1.0-alpha.0.6+84c76d1142ea4d + # + # TODO: We continue calling this "git version" because so many + # downstream consumers are expecting it there. + # + # These regexes are painful enough in sed... + # We don't want to do them in pure shell, so disable SC2001 + # shellcheck disable=SC2001 + DASHES_IN_VERSION=$(echo "${KUBE_GIT_VERSION}" | sed "s/[^-]//g") + if [[ "${DASHES_IN_VERSION}" == "---" ]] ; then + # shellcheck disable=SC2001 + # We have distance to subversion (v1.1.0-subversion-1-gCommitHash) + KUBE_GIT_VERSION=$(echo "${KUBE_GIT_VERSION}" | sed "s/-\([0-9]\{1,\}\)-g\([0-9a-f]\{14\}\)$/.\1\+\2/") + elif [[ "${DASHES_IN_VERSION}" == "--" ]] ; then + # shellcheck disable=SC2001 + # We have distance to base tag (v1.1.0-1-gCommitHash) + KUBE_GIT_VERSION=$(echo "${KUBE_GIT_VERSION}" | sed "s/-g\([0-9a-f]\{14\}\)$/+\1/") + fi + if [[ "${KUBE_GIT_TREE_STATE}" == "dirty" ]]; then + # git describe --dirty only considers changes to existing files, but + # that is problematic since new untracked .go files affect the build, + # so use our idea of "dirty" from git status instead. + KUBE_GIT_VERSION+="-dirty" + fi + + + # Try to match the "git describe" output to a regex to try to extract + # the "major" and "minor" versions and whether this is the exact tagged + # version or whether the tree is between two tagged versions. + if [[ "${KUBE_GIT_VERSION}" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?([-].*)?([+].*)?$ ]]; then + KUBE_GIT_MAJOR=${BASH_REMATCH[1]} + KUBE_GIT_MINOR=${BASH_REMATCH[2]} + if [[ -n "${BASH_REMATCH[4]}" ]]; then + KUBE_GIT_MINOR+="+" + fi + fi + + # If KUBE_GIT_VERSION is not a valid Semantic Version, then refuse to build. + if ! [[ "${KUBE_GIT_VERSION}" =~ ^v([0-9]+)\.([0-9]+)(\.[0-9]+)?(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then + echo "KUBE_GIT_VERSION should be a valid Semantic Version. Current value: ${KUBE_GIT_VERSION}" + echo "Please see more details here: https://semver.org" + exit 1 + fi + fi + fi +} \ No newline at end of file diff --git a/pkg/apis/cluster/v1alpha1/cluster_types.go b/pkg/apis/cluster/v1alpha1/cluster_types.go index 7cc0eb8a3..507eddd1d 100644 --- a/pkg/apis/cluster/v1alpha1/cluster_types.go +++ b/pkg/apis/cluster/v1alpha1/cluster_types.go @@ -10,7 +10,7 @@ const ( ResourcesSingularCluster = "cluster" ResourcesPluralCluster = "clusters" - IsHostCluster = "cluster.kubesphere.io/is-host-cluster" + HostCluster = "cluster-role.kubesphere.io/host" // Description of which region the cluster been placed ClusterRegion = "cluster.kubesphere.io/region" // Name of the cluster group diff --git a/pkg/apiserver/dispatch/dispatch.go b/pkg/apiserver/dispatch/dispatch.go index 6696c689c..a3a4703ac 100644 --- a/pkg/apiserver/dispatch/dispatch.go +++ b/pkg/apiserver/dispatch/dispatch.go @@ -260,10 +260,8 @@ func isClusterReady(cluster *clusterv1alpha1.Cluster) bool { } func isClusterHostCluster(cluster *clusterv1alpha1.Cluster) bool { - for key, value := range cluster.Annotations { - if key == clusterv1alpha1.IsHostCluster && value == "true" { - return true - } + if _, ok := cluster.Labels[clusterv1alpha1.HostCluster]; ok { + return true } return false diff --git a/pkg/controller/cluster/cluster_controller.go b/pkg/controller/cluster/cluster_controller.go index 04cf21012..30fd67966 100644 --- a/pkg/controller/cluster/cluster_controller.go +++ b/pkg/controller/cluster/cluster_controller.go @@ -70,8 +70,35 @@ const ( // proxy format proxyFormat = "%s/api/v1/namespaces/kubesphere-system/services/:ks-apiserver:80/proxy/%s" + + // mulitcluster configuration name + configzMultiCluster = "multicluster" ) +// Cluster template for reconcile host cluster if there is none. +var hostCluster = &clusterv1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "host", + Annotations: map[string]string{ + "kubesphere.io/description": "Automatically created by kubesphere, " + + "we encourage you use host cluster for cluster management only, " + + "deploy workloads to member clusters.", + }, + Labels: map[string]string{ + clusterv1alpha1.HostCluster: "", + clusterv1alpha1.ClusterGroup: "production", + }, + }, + Spec: clusterv1alpha1.ClusterSpec{ + JoinFederation: true, + Enable: true, + Provider: "kubesphere", + Connection: clusterv1alpha1.Connection{ + Type: clusterv1alpha1.ConnectionTypeDirect, + }, + }, +} + // ClusterData stores cluster client type clusterData struct { @@ -176,11 +203,17 @@ func (c *clusterController) Run(workers int, stopCh <-chan struct{}) error { go wait.Until(c.worker, c.workerLoopPeriod, stopCh) } + // refresh cluster configz every 2 minutes go wait.Until(func() { if err := c.syncStatus(); err != nil { klog.Errorf("Error periodically sync cluster status, %v", err) } - }, 5*time.Minute, stopCh) + + if err := c.reconcileHostCluster(); err != nil { + klog.Errorf("Error create host cluster, error %v", err) + } + + }, 2*time.Minute, stopCh) <-stopCh return nil @@ -256,6 +289,26 @@ func (c *clusterController) syncStatus() error { return nil } +// reconcileHostCluster will create a host cluster if there are no clusters labeled 'cluster-role.kubesphere.io/host' +func (c *clusterController) reconcileHostCluster() error { + clusters, err := c.clusterLister.List(labels.SelectorFromSet(labels.Set{clusterv1alpha1.HostCluster: ""})) + if err != nil { + return err + } + + if len(clusters) == 0 { + hostKubeConfig, err := buildKubeconfigFromRestConfig(c.hostConfig) + if err != nil { + return err + } + hostCluster.Spec.Connection.KubeConfig = hostKubeConfig + _, err = c.clusterClient.Create(hostCluster) + return err + } + + return nil +} + func (c *clusterController) syncCluster(key string) error { startTime := time.Now() @@ -524,6 +577,14 @@ func (c *clusterController) syncCluster(key string) error { cluster.Status.Configz = configz } + // label cluster host cluster if configz["multicluster"]==true, this is + if mc, ok := configz[configzMultiCluster]; ok && mc && c.checkIfClusterIsHostCluster(nodes) { + if cluster.Labels == nil { + cluster.Labels = make(map[string]string) + } + cluster.Labels[clusterv1alpha1.HostCluster] = "" + } + clusterReadyCondition := clusterv1alpha1.ClusterCondition{ Type: clusterv1alpha1.ClusterReady, Status: v1.ConditionTrue, @@ -577,6 +638,27 @@ func (c *clusterController) syncCluster(key string) error { return nil } +func (c *clusterController) checkIfClusterIsHostCluster(memberClusterNodes *v1.NodeList) bool { + hostNodes, err := c.client.CoreV1().Nodes().List(metav1.ListOptions{}) + if err != nil { + return false + } + + if hostNodes == nil || memberClusterNodes == nil { + return false + } + + if len(hostNodes.Items) != len(memberClusterNodes.Items) { + return false + } + + if len(hostNodes.Items) > 0 && (hostNodes.Items[0].Status.NodeInfo.MachineID != memberClusterNodes.Items[0].Status.NodeInfo.MachineID) { + return false + } + + return true +} + // tryToFetchKubeSphereComponents will send requests to member cluster configz api using kube-apiserver proxy way func (c *clusterController) tryToFetchKubeSphereComponents(host string, transport http.RoundTripper) (map[string]bool, error) { client := http.Client{ @@ -671,16 +753,6 @@ func (c *clusterController) updateClusterCondition(cluster *clusterv1alpha1.Clus } } -func isHostCluster(cluster *clusterv1alpha1.Cluster) bool { - for k, v := range cluster.Annotations { - if k == clusterv1alpha1.IsHostCluster && v == "true" { - return true - } - } - - return false -} - // joinFederation joins a cluster into federation clusters. // return nil error if kubefed cluster already exists. func (c *clusterController) joinFederation(clusterConfig *rest.Config, joiningClusterName string, labels map[string]string) (*fedv1b1.KubeFedCluster, error) { diff --git a/pkg/controller/cluster/helper.go b/pkg/controller/cluster/helper.go index 916b1b53b..396629b67 100644 --- a/pkg/controller/cluster/helper.go +++ b/pkg/controller/cluster/helper.go @@ -1 +1,37 @@ package cluster + +import ( + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/tools/clientcmd/api" +) + +func buildKubeconfigFromRestConfig(config *rest.Config) ([]byte, error) { + apiConfig := api.NewConfig() + + apiConfig.Clusters["kubernetes"] = &api.Cluster{ + Server: config.Host, + CertificateAuthorityData: config.CAData, + CertificateAuthority: config.CAFile, + } + + apiConfig.AuthInfos["kubernetes-admin"] = &api.AuthInfo{ + ClientCertificate: config.CertFile, + ClientCertificateData: config.CertData, + ClientKey: config.KeyFile, + ClientKeyData: config.KeyData, + TokenFile: config.BearerTokenFile, + Token: config.BearerToken, + Username: config.Username, + Password: config.Password, + } + + apiConfig.Contexts["kubernetes-admin@kubernetes"] = &api.Context{ + Cluster: "kubernetes", + AuthInfo: "kubernetes-admin", + } + + apiConfig.CurrentContext = "kubernetes-admin@kubernetes" + + return clientcmd.Write(*apiConfig) +} diff --git a/pkg/version/version.go b/pkg/version/version.go index 4b16a4c22..8c55f7371 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -24,14 +24,18 @@ import ( ) var ( - version = "v0.0.0" + gitVersion = "v0.0.0" gitCommit = "unknown" gitTreeState = "unknown" buildDate = "unknown" + gitMajor = "unknown" + gitMinor = "unknown" ) type Info struct { - Version string `json:"gitVersion"` + GitVersion string `json:"gitVersion"` + GitMajor string `json:"gitMajor"` + GitMinor string `json:"gitMinor"` GitCommit string `json:"gitCommit"` GitTreeState string `json:"gitTreeState"` BuildDate string `json:"buildDate"` @@ -47,7 +51,8 @@ func Get() Info { // These variables typically come from -ldflags settings and // in their absence fallback to the default settings return Info{ - Version: version, + GitVersion: gitVersion, + GitCommit: gitCommit, GitTreeState: gitTreeState, BuildDate: buildDate,