diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..8a3660a18c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +**/dist/* +tests/ +images/ +tools/ +docs/ +contrib/ diff --git a/Dockerfile-dev b/Dockerfile-dev new file mode 100644 index 0000000000..240b667a4c --- /dev/null +++ b/Dockerfile-dev @@ -0,0 +1,24 @@ +FROM golang:1.12-stretch as BUILD + +WORKDIR /go/src/github.com/ovh/cds + +RUN apt-get update \ + && apt-get install -y libsecret-1-dev +# separating for quicker build times since its for dev + +COPY . . + +RUN OS=linux ARCH=amd64 make build \ + && rm -rf /var/lib/apt/lists/* + +FROM debian:jessie + +USER nobody +WORKDIR /app + +COPY --from=BUILD --chown=nobody:nogroup /go/src/github.com/ovh/cds/engine/dist/* ./ +COPY --from=BUILD --chown=nobody:nogroup /go/src/github.com/ovh/cds/engine/worker/dist/* ./ +COPY --from=BUILD --chown=nobody:nogroup /go/src/github.com/ovh/cds/cli/cdsctl/dist/* ./ +COPY --chown=nobody:nogroup engine/sql sql + +CMD ["/app/cds-engine-linux-amd64"] diff --git a/contrib/helm/cds/README.md b/contrib/helm/cds/README.md index 84f6048ccf..acd6687dd4 100644 --- a/contrib/helm/cds/README.md +++ b/contrib/helm/cds/README.md @@ -9,15 +9,14 @@ Documentation is available at https://ovh.github.io/cds/ ```console cd contrib/helm/cds # To let CDS spawn workers on your kubernetes cluster you need to copy your kubeconfig.yaml in the current directory -cp yourPathToKubeconfig.yaml kubeconfig.yaml helm dependency update helm install . ``` - ## FUTURE When CDS helm chart will be released you'll be able to install with + ```console $ helm install stable/cds ``` @@ -36,9 +35,9 @@ It starts a PostgreSQL server, a Redis server and an Elasticsearch server using ## Prerequisites -- Kubernetes 1.4+ with Beta APIs enabled -- PV provisioner support in the underlying infrastructure -- Kubernetes config file (`kubeconfig.yaml`) located at this path. (For minikube it's often located `~/.kube/config`) ++ Kubernetes 1.4+ with Beta APIs enabled ++ PV provisioner support in the underlying infrastructure ++ RBAC enabled or .rbac.create set to false ## Installing the Chart @@ -46,10 +45,8 @@ To install the chart with the release name `my-release`: ```console # Inside of cds/contrib/helm/cds -# To let CDS spawn workers on your kubernetes cluster you need to copy your kubeconfig.yaml in the current directory -cp yourPathToKubeconfig.yaml kubeconfig.yaml -helm dependency update -helm install --name my-cds . +$ helm dependency update +$ helm install --name my-cds . ``` The command deploys CDS on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. @@ -153,7 +150,6 @@ And check in the log of your api server to get registration URL : export CDS_API_POD_NAME=$(kubectl get pods --namespace default -l "app=my-cds-cds-api" -o jsonpath="{.items[0].metadata.name}") kubectl logs -f --namespace default $CDS_API_POD_NAME | grep 'account/verify' - ``` + Create the first CDS User @@ -187,8 +183,8 @@ $ chmod +x cdsctl *please note that the version linux/amd64, darwin/amd64 and windows/amd64 use libsecret / keychain to store the CDS Password. If you don't want to use the keychain, you can select the version i386* - + Login with cdsctl + ```console $ ./cdsctl login --api-url http://$SERVICE_IP/cdsapi -u yourusername CDS API URL: http://$SERVICE_IP/cdsapi @@ -282,13 +278,9 @@ $ helm install --name my-release -f values.yaml . > **Tip**: You can use the default [values.yaml](values.yaml) + If you use a Kubernetes provider without LoadBalancer ability you just have to set your `ui.serviceType` to `ClusterIP` and set `ingress.enabled` to `true` with the right `ingress.hostname` and `ingress.port` (for example: `helm install --kubeconfig kubeconfig.yml --name my-release -f values.yaml --set ui.serviceType=ClusterIP --set ingress.enabled=true --set ingress.hostname=cds.MY_NODES_URL --set ingress.port=32080 .`). - + If you use a minikube you have to set `ui.serviceType` to `ClusterIP`. - + If you use a Kubernetes as GKE, EKS or if your cloud provider provide you an available LoadBalancer you just have to set `ui.serviceType` to `LoadBalancer`. -+ Your `kubeconfig.yaml` must be located in this directory. - ## Image The `image` parameter allows specifying which image will be pulled for the chart. @@ -303,6 +295,6 @@ By default, cds api artifact directory is created as default PersistentVolumeCla 1. Create the PersistentVolumeClaim 1. Install the chart -```bash +```console $ helm install --name my-release --set cds.existingClaim=PVC_NAME . ``` diff --git a/contrib/helm/cds/templates/_helpers.tpl b/contrib/helm/cds/templates/_helpers.tpl index ab6de9b54d..ba746087b2 100644 --- a/contrib/helm/cds/templates/_helpers.tpl +++ b/contrib/helm/cds/templates/_helpers.tpl @@ -38,3 +38,14 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this {{- define "cds.elasticsearch.fullname" -}} {{- printf "%s-%s" .Release.Name "elasticsearch" | trunc 63 | trimSuffix "-" -}} {{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "cds.serviceAccount.name" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "cds.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/contrib/helm/cds/templates/rbac.yaml b/contrib/helm/cds/templates/rbac.yaml new file mode 100644 index 0000000000..c0edad38d8 --- /dev/null +++ b/contrib/helm/cds/templates/rbac.yaml @@ -0,0 +1,36 @@ +{{- if .Values.rbac.create -}} +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: Role +metadata: + name: {{ template "cds.fullname" . }} + labels: + app: {{ template "cds.fullname" . }}-api + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +rules: + - apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "create", "update", "delete"] + - apiGroups: [""] + resources: ["namespaces"] + verbs: ["list", "create"] +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: RoleBinding +metadata: + name: {{ template "cds.fullname" . }} + labels: + app: {{ template "cds.fullname" . }}-api + chart: {{ .Chart.Name }}-{{ .Chart.Version }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "cds.fullname" . }} +subjects: + - name: {{ template "cds.serviceAccount.name" . }} + namespace: {{ .Release.Namespace | quote }} + kind: ServiceAccount +{{- end -}} diff --git a/contrib/helm/cds/templates/secrets.yaml b/contrib/helm/cds/templates/secrets.yaml index 44156d0165..1384ad1d98 100644 --- a/contrib/helm/cds/templates/secrets.yaml +++ b/contrib/helm/cds/templates/secrets.yaml @@ -20,4 +20,3 @@ data: cds-api_secrets_key: {{ randAlphaNum 32 | b64enc | quote }} {{ end }} cds_config_file: {{ .Files.Get "config.toml" | b64enc | b64enc }} - cds-k8s_config_file: {{ .Files.Get "kubeconfig.yaml" | b64enc | b64enc }} diff --git a/contrib/helm/cds/templates/serviceaccount.yaml b/contrib/helm/cds/templates/serviceaccount.yaml new file mode 100644 index 0000000000..0142aba6b2 --- /dev/null +++ b/contrib/helm/cds/templates/serviceaccount.yaml @@ -0,0 +1,15 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +{{- if .Values.image.imagePullSecrets }} +imagePullSecrets: {{ toYaml .Values.image.imagePullSecrets | nindent 2 }} +{{- end }} +metadata: + name: {{ template "cds.serviceAccount.name" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + app: {{ template "cds.fullname" . }}-api + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" + {{- end -}} diff --git a/contrib/helm/cds/values.yaml b/contrib/helm/cds/values.yaml index 9368406afa..2f19058d31 100644 --- a/contrib/helm/cds/values.yaml +++ b/contrib/helm/cds/values.yaml @@ -21,6 +21,16 @@ image: cdsUsername: cds logLevel: info +rbac: + create: true + +serviceAccount: + # Specifies whether a service account should be created + create: true + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: + ## Default: random 64 character string #apiAuthSharedinfratoken: changeitchangeitchangeitchangeitchangeitchangeitchangeitchangeit diff --git a/engine/hatchery/kubernetes/kubernetes.go b/engine/hatchery/kubernetes/kubernetes.go index 762dc9faeb..27f5cd59d3 100644 --- a/engine/hatchery/kubernetes/kubernetes.go +++ b/engine/hatchery/kubernetes/kubernetes.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "k8s.io/client-go/tools/clientcmd" + "github.com/gorilla/mux" apiv1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -17,7 +19,6 @@ import ( "k8s.io/client-go/kubernetes" _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "github.com/ovh/cds/engine/api" @@ -59,47 +60,62 @@ func (h *HatcheryKubernetes) ApplyConfiguration(cfg interface{}) error { } var errCl error - var clientset *kubernetes.Clientset + var clientSet *kubernetes.Clientset k8sTimeout := time.Second * 10 - if h.Config.KubernetesConfigFile != "" { - cfg, err := clientcmd.BuildConfigFromFlags(h.Config.KubernetesMasterURL, h.Config.KubernetesConfigFile) - if err != nil { - return sdk.WrapError(err, "Cannot build config from flags") - } - cfg.Timeout = k8sTimeout - clientset, errCl = kubernetes.NewForConfig(cfg) - if errCl != nil { - return sdk.WrapError(errCl, "Cannot create client with newForConfig") + if h.Config.KubernetesMasterURL != "" { + if h.Config.KubernetesConfigFile != "" { + cfg, err := clientcmd.BuildConfigFromFlags(h.Config.KubernetesMasterURL, h.Config.KubernetesConfigFile) + if err != nil { + return sdk.WrapError(err, "Cannot build config from flags") + } + cfg.Timeout = k8sTimeout + + clientSet, errCl = kubernetes.NewForConfig(cfg) + if errCl != nil { + return sdk.WrapError(errCl, "Cannot create client with newForConfig") + } + } else { + configK8s, err := clientcmd.BuildConfigFromKubeconfigGetter(h.Config.KubernetesMasterURL, h.getStartingConfig) + if err != nil { + return sdk.WrapError(err, "Cannot build config from config getter") + } + configK8s.Timeout = k8sTimeout + + if h.Config.KubernetesCertAuthData != "" { + configK8s.TLSClientConfig = rest.TLSClientConfig{ + CAData: []byte(h.Config.KubernetesCertAuthData), + CertData: []byte(h.Config.KubernetesClientCertData), + KeyData: []byte(h.Config.KubernetesClientKeyData), + } + } + + // creates the clientset + clientSet, errCl = kubernetes.NewForConfig(configK8s) + if errCl != nil { + return sdk.WrapError(errCl, "Cannot create new config") + } } } else { - configK8s, err := clientcmd.BuildConfigFromKubeconfigGetter(h.Config.KubernetesMasterURL, h.getStartingConfig) + config, err := rest.InClusterConfig() if err != nil { - return sdk.WrapError(err, "Cannot build config from config getter") - } - configK8s.Timeout = k8sTimeout - - if h.Config.KubernetesCertAuthData != "" { - configK8s.TLSClientConfig = rest.TLSClientConfig{ - CAData: []byte(h.Config.KubernetesCertAuthData), - CertData: []byte(h.Config.KubernetesClientCertData), - KeyData: []byte(h.Config.KubernetesClientKeyData), - } + return sdk.WrapError(err, "Unable to configure k8s InClusterConfig") } - // creates the clientset - clientset, errCl = kubernetes.NewForConfig(configK8s) + clientSet, errCl = kubernetes.NewForConfig(config) if errCl != nil { - return sdk.WrapError(errCl, "Cannot create new config") + return sdk.WrapError(errCl, "Unable to configure k8s client with InClusterConfig") } + } - h.k8sClient = clientset + + h.k8sClient = clientSet if h.Config.Namespace != apiv1.NamespaceDefault { - if _, err := clientset.CoreV1().Namespaces().Get(h.Config.Namespace, metav1.GetOptions{}); err != nil { + if _, err := clientSet.CoreV1().Namespaces().Get(h.Config.Namespace, metav1.GetOptions{}); err != nil { ns := apiv1.Namespace{} ns.SetName(h.Config.Namespace) - if _, errC := clientset.CoreV1().Namespaces().Create(&ns); errC != nil { + if _, errC := clientSet.CoreV1().Namespaces().Create(&ns); errC != nil { return sdk.WrapError(errC, "Cannot create namespace %s in kubernetes", h.Config.Namespace) } } @@ -173,10 +189,6 @@ func (h *HatcheryKubernetes) CheckConfiguration(cfg interface{}) error { return fmt.Errorf("please enter a valid kubernetes namespace") } - if hconfig.KubernetesMasterURL == "" && hconfig.KubernetesConfigFile == "" { - return fmt.Errorf("please enter a valid kubernetes master URL or provide a kubernetes config file") - } - return nil } diff --git a/ui/package.json b/ui/package.json index c84cf4ae00..c1a0a5f020 100644 --- a/ui/package.json +++ b/ui/package.json @@ -35,41 +35,41 @@ }, "private": true, "dependencies": { - "@angular/animations": "7.2.2", - "@angular/common": "7.2.2", - "@angular/compiler": "7.2.2", - "@angular/core": "7.2.2", - "@angular/forms": "7.2.2", - "@angular/http": "7.2.2", - "@angular/platform-browser": "7.2.2", - "@angular/platform-browser-dynamic": "7.2.2", - "@angular/router": "7.2.2", + "@angular/animations": "7.2.14", + "@angular/common": "7.2.14", + "@angular/compiler": "7.2.14", + "@angular/core": "7.2.14", + "@angular/forms": "7.2.14", + "@angular/http": "7.2.14", + "@angular/platform-browser": "7.2.14", + "@angular/platform-browser-dynamic": "7.2.14", + "@angular/router": "7.2.14", "@ngui/auto-complete": "2.0.0", "@ngx-translate/core": "11.0.1", "@ngx-translate/http-loader": "4.0.0", - "@ngxs/devtools-plugin": "3.3.4", - "@ngxs/logger-plugin": "3.3.4", - "@ngxs/store": "3.3.4", - "@swimlane/ngx-charts": "10.0.0", - "@types/dagre-d3": "0.4.38", + "@ngxs/devtools-plugin": "3.4.3", + "@ngxs/logger-plugin": "3.4.3", + "@ngxs/store": "3.4.3", + "@swimlane/ngx-charts": "10.1.0", + "@types/dagre-d3": "0.4.39", "angular2-moment": "1.9.0", "angular2-prettyjson": "3.0.1", "angular2-toaster": "7.0.0", "animate.css": "3.7.0", - "ansi_up": "3.0.0", - "codemirror": "5.43.0", - "core-js": "2.6.3", - "d3": "5.7.0", + "ansi_up": "4.0.3", + "codemirror": "5.46.0", + "core-js": "3.0.1", + "d3": "5.9.2", "d3-zoom": "1.7.3", "dagre-d3": "0.6.3", "diff": "4.0.1", "dragula": "3.7.2", "duration-js": "4.0.0", "enhanced-resolve": "4.1.0", - "fast-json-patch": "2.0.7", + "fast-json-patch": "2.1.0", "font-awesome": "4.7.0", "immutable": "4.0.0-rc.12", - "js-beautify": "1.8.9", + "js-beautify": "1.9.1", "lodash": "4.17.11", "moment": "2.24.0", "ng-event-source": "1.0.14", @@ -79,49 +79,49 @@ "ng2-dragula": "2.1.1", "ng2-semantic-ui": "git+https://git@github.com/bnjjj/ng2-semantic-ui.git#dist", "ngx-auto-scroll": "1.1.0", - "ngx-clipboard": "11.1.9", - "ngx-infinite-scroll": "7.0.1", - "ngx-markdown": "7.1.2", - "prismjs": "1.15.0", + "ngx-clipboard": "12.0.0", + "ngx-infinite-scroll": "7.1.0", + "ngx-markdown": "7.1.5", + "prismjs": "1.16.0", "raven-js": "3.27.0", - "rxjs": "6.3.3", - "rxjs-compat": "6.3.3", - "sanitize-html": "1.20.0", - "semantic-ui": "2.2.13", + "rxjs": "6.5.1", + "rxjs-compat": "6.5.1", + "sanitize-html": "1.20.1", + "semantic-ui": "2.4.2", "spinkit": "1.2.5", "string-format-obj": "1.1.1", "ts-helpers": "1.1.2", - "zone.js": "0.8.29" + "zone.js": "0.9.0" }, "devDependencies": { - "@angular-devkit/build-angular": "0.12.3", - "@angular/cli": "7.2.3", - "@angular/compiler-cli": "7.2.2", - "@compodoc/compodoc": "1.1.7", - "@sentry/cli": "1.37.4", - "@types/d3": "5.7.0", - "@types/dagre": "0.7.40", - "@types/jasmine": "3.3.8", + "@angular-devkit/build-angular": "0.13.8", + "@angular/cli": "7.3.8", + "@angular/compiler-cli": "7.2.14", + "@compodoc/compodoc": "1.1.9", + "@sentry/cli": "1.41.2", + "@types/d3": "5.7.2", + "@types/dagre": "0.7.41", + "@types/jasmine": "3.3.12", "@types/jasminewd2": "2.0.6", - "@types/lodash": "4.14.120", - "@types/node": "10.12.18", - "codelyzer": "4.5.0", - "copy-webpack-plugin": "4.6.0", - "husky": "1.3.1", - "jasmine-core": "3.3.0", + "@types/lodash": "4.14.123", + "@types/node": "11.13.8", + "codelyzer": "5.0.1", + "copy-webpack-plugin": "5.0.3", + "husky": "2.1.0", + "jasmine-core": "3.4.0", "jasmine-spec-reporter": "4.2.1", - "karma": "4.0.0", + "karma": "4.1.0", "karma-chrome-launcher": "2.2.0", - "karma-coverage-istanbul-reporter": "2.0.4", + "karma-coverage-istanbul-reporter": "2.0.5", "karma-jasmine": "2.0.1", - "karma-jasmine-html-reporter": "1.4.0", + "karma-jasmine-html-reporter": "1.4.2", "karma-junit-reporter": "1.2.0", "lint-staged": "git+https://github.com/bnjjj/lint-staged.git#master", "node-sass": "4.11.0", - "ts-node": "8.0.2", - "tslint": "5.12.1", - "typescript": "3.2.4", - "webdriver-manager": "12.1.1", + "ts-node": "8.1.0", + "tslint": "5.16.0", + "typescript": "3.4.5", + "webdriver-manager": "12.1.2", "wrench-sui": "0.0.3" } } diff --git a/ui/sonar-project.properties b/ui/sonar-project.properties index 1b02549835..745760f552 100644 --- a/ui/sonar-project.properties +++ b/ui/sonar-project.properties @@ -11,4 +11,4 @@ sonar.coverage.exclusions=**/*.spec.ts sonar.links.homepage=https://github.com/ovh/cds sonar.links.scm=https://github.com/ovh/cds -sonar.links.issue=https://github.com/ovh/cds/issues \ No newline at end of file +sonar.links.issue=https://github.com/ovh/cds/issues