Bootstrap container code for GCP
This commit provides the Go code and scripts for the Bootstrap container for GCP. The Bootstrap container (bootstrap_capg) for Google Cloud is designed to accept three commands: create, delete and help. - create - will create an Ephemeral GKE cluster in Google Cloud - delete - will delete the Ephemeral GKE cluster from the Google Cloud - help - Stdout the help text for using this container. Please, refer to the bootstrap_capg/README.md for further details. Change-Id: Ifad7c7a7fede0230029716c9e093449f5886e859
This commit is contained in:
parent
f45d429c3c
commit
aa624ce27c
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
*build/
|
*build/
|
||||||
|
bootstrap_capg/.vscode/launch.json
|
||||||
|
bootstrap_capg/go.mod
|
||||||
|
56
bootstrap_capg/Dockerfile
Normal file
56
bootstrap_capg/Dockerfile
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Copyright 2018 AT&T Intellectual Property. All other rights reserved.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
ARG GCP_SDK=gcr.io/google.com/cloudsdktool/cloud-sdk:308.0.0
|
||||||
|
ARG GOLANG=golang:1.14.4
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Build GCP Bootstrap Container application
|
||||||
|
############################################
|
||||||
|
FROM ${GOLANG} as builder
|
||||||
|
WORKDIR /home/build
|
||||||
|
# copy the capg bootstrap container app code
|
||||||
|
COPY main.go .
|
||||||
|
COPY config/ config/
|
||||||
|
# Build capg bootstrap container application
|
||||||
|
RUN go mod init opendev.org/airship/images/bootstrap_capg && \
|
||||||
|
go get -d -v ./... && \
|
||||||
|
go install . && \
|
||||||
|
CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o capg-ephemeral .
|
||||||
|
|
||||||
|
############################################
|
||||||
|
# Run GCP Bootstrap Container
|
||||||
|
############################################
|
||||||
|
FROM ${GCP_SDK}
|
||||||
|
LABEL org.opencontainers.image.authors='airship-discuss@lists.airshipit.org, irc://#airshipit@freenode' \
|
||||||
|
org.opencontainers.image.url='https://airshipit.org' \
|
||||||
|
org.opencontainers.image.documentation='https://opendev.org/airship/images/src/branch/master/bootstrap_capg/README.md' \
|
||||||
|
org.opencontainers.image.source='https://opendev.org/airship/images' \
|
||||||
|
org.opencontainers.image.vendor='The Airship Authors' \
|
||||||
|
org.opencontainers.image.licenses='Apache-2.0'
|
||||||
|
|
||||||
|
RUN adduser --disabled-password bootstrap
|
||||||
|
USER bootstrap
|
||||||
|
|
||||||
|
WORKDIR /home/bootstrap
|
||||||
|
ENV HOME=/home/bootstrap
|
||||||
|
ENV PATH="${PATH}:${HOME}"
|
||||||
|
|
||||||
|
# Copy the Google Cloud Bootstrap Container command
|
||||||
|
COPY --from=builder /home/build/capg-ephemeral .
|
||||||
|
# Copy help file
|
||||||
|
COPY assets/help.txt .
|
||||||
|
|
||||||
|
# # Executes the script to create the GKE cluster
|
||||||
|
CMD ["capg-ephemeral"]
|
56
bootstrap_capg/Makefile
Normal file
56
bootstrap_capg/Makefile
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
SHELL := /bin/bash
|
||||||
|
PUSH_IMAGE ?= false
|
||||||
|
GIT_VERSION ?= v0.1.0
|
||||||
|
GIT_MODULE ?= opendev.org/airship/airshipctl/pkg/version
|
||||||
|
|
||||||
|
GO_FLAGS := -ldflags '-extldflags "-static"' -tags=netgo
|
||||||
|
GO_FLAGS += -ldflags "-X ${GIT_MODULE}.gitVersion=${GIT_VERSION}"
|
||||||
|
|
||||||
|
DOCKER_MAKE_TARGET := build
|
||||||
|
|
||||||
|
# docker image options
|
||||||
|
DOCKER_REGISTRY ?= quay.io
|
||||||
|
DOCKER_FORCE_CLEAN ?= true
|
||||||
|
DOCKER_IMAGE_NAME ?= capg-bootstrap
|
||||||
|
DOCKER_IMAGE_PREFIX ?= airshipit
|
||||||
|
DOCKER_IMAGE_TAG ?= latest
|
||||||
|
DOCKER_IMAGE ?= $(DOCKER_REGISTRY)/$(DOCKER_IMAGE_PREFIX)/$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)
|
||||||
|
DOCKER_TARGET_STAGE ?= release
|
||||||
|
|
||||||
|
PATH += :/usr/local/go/bin
|
||||||
|
HELP_FILE ?= /tmp/help.txt
|
||||||
|
ORIGINAL_HELP_FILE = ./assets/help.txt
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: images
|
||||||
|
|
||||||
|
.PHONY: images
|
||||||
|
images: Dockerfile \
|
||||||
|
main.go \
|
||||||
|
config/gke_cluster.go \
|
||||||
|
config/gcp_cluster.go \
|
||||||
|
config/gcp_config.go \
|
||||||
|
assets/help.txt
|
||||||
|
@docker build . --network=host \
|
||||||
|
--build-arg MAKE_TARGET=$(DOCKER_MAKE_TARGET) \
|
||||||
|
--tag $(DOCKER_IMAGE) \
|
||||||
|
--force-rm=$(DOCKER_FORCE_CLEAN)
|
||||||
|
ifeq ($(PUSH_IMAGE), true)
|
||||||
|
docker push $(IMAGE)
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
@docker image rm $(DOCKER_IMAGE)
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
@echo TODO
|
||||||
|
|
||||||
|
# style checks
|
||||||
|
.PHONY: tests
|
||||||
|
tests: images
|
||||||
|
if [ -f $(HELP_FILE) ]; then sudo rm $(HELP_FILE); fi
|
||||||
|
cp gcp-config.yaml /tmp
|
||||||
|
docker run -v /tmp:/kube --env-file bootstrap-env.list --name capg-test $(DOCKER_IMAGE)
|
||||||
|
cmp $(HELP_FILE) $(ORIGINAL_HELP_FILE)
|
32
bootstrap_capg/README.md
Normal file
32
bootstrap_capg/README.md
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# GCP Bootstrap Container
|
||||||
|
|
||||||
|
This project contains the Go application and configuration files for
|
||||||
|
implementing the GCP Bootstrap container.
|
||||||
|
|
||||||
|
The GCP Bootstrap container is responsible to create or delete a Kubernetes
|
||||||
|
(K8S) cluster on GCP Cloud platform using the GKE (Google Kubernetes Engine).
|
||||||
|
|
||||||
|
## Go Application
|
||||||
|
|
||||||
|
The Go application is the bootstrap container orchestrator that is responsible
|
||||||
|
for translating commands into actions: create, delete, help.
|
||||||
|
|
||||||
|
This Go application uses the Ephemeral cluster configuration file
|
||||||
|
(e.g., gcp-config.yaml) to determine the Google Cloud credentials and
|
||||||
|
data to use to create or delete the ephemeral cluster.
|
||||||
|
|
||||||
|
## Dockerfile
|
||||||
|
|
||||||
|
The **Dockerfile** uses a multi-stage builds to first build the Go application
|
||||||
|
then create the GCP bootstrap container image.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
To build the bootstrap container image, execute the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make images
|
||||||
|
```
|
||||||
|
|
||||||
|
This command will build the Go application and then create the bootstrap
|
||||||
|
container image.
|
66
bootstrap_capg/assets/help.txt
Normal file
66
bootstrap_capg/assets/help.txt
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
|
||||||
|
Google Cloud Ephemeral Configuration File Definition
|
||||||
|
-----------------------------------------------------
|
||||||
|
The GCP Bootstrap container creates an Ephemeral K8S cluster on the Google Cloud platform.
|
||||||
|
The container requires authentication credentials and other information about the cluster to deploy.
|
||||||
|
It requires a YAML configuration file with the format provided below.
|
||||||
|
|
||||||
|
<YAML>
|
||||||
|
apiVersion: v1
|
||||||
|
kind: GoogleCloudConfig
|
||||||
|
metadata:
|
||||||
|
name: <Ephemeral K8S cluster name>
|
||||||
|
credentials:
|
||||||
|
project: <Google Cloud Project ID>
|
||||||
|
account: <Google Cloud Account ID>
|
||||||
|
credential: <credentials.json filename>
|
||||||
|
spec:
|
||||||
|
region: <Google Cloud Region, e.g., us-central1>
|
||||||
|
Zone: <Google Cloud Zone, e.g., us-central1-c>
|
||||||
|
cluster:
|
||||||
|
k8sVersion: <Kubernetes version, e.g., 1.16.9-gke.6>
|
||||||
|
machineSize: <Google Cloud Compute VM Type, e.g., e2-medium>
|
||||||
|
diskSize: <Google Cloud compute disk size>
|
||||||
|
replicas: <Node Replica Number for the cluster>
|
||||||
|
kubeconfig: <Kubeconfig filename, Default is 'kubeconfig'>
|
||||||
|
</YMAL>
|
||||||
|
|
||||||
|
The JSON format is also a valid configuration file for this bootstrap container.
|
||||||
|
|
||||||
|
<JSON>
|
||||||
|
{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "GoogleCloudConfig",
|
||||||
|
"metadata": {
|
||||||
|
"name": "<Ephemeral K8S cluster name>"
|
||||||
|
},
|
||||||
|
"credentials": {
|
||||||
|
"project": "<Google Cloud Project ID>",
|
||||||
|
"account": "<Google Cloud Account ID>",
|
||||||
|
"credential": "<credentials.json filename>"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"region": "<Google Cloud Region, e.g., us-central1>",
|
||||||
|
"zone": "<Google Cloud Compute VM Type, e.g., e2-medium>",
|
||||||
|
"cluster": {
|
||||||
|
"k8sVersion": "<Kubernetes version, e.g., 1.16.9-gke.6>",
|
||||||
|
"machineSize": "<Google Cloud Compute VM Type, e.g., e2-medium>",
|
||||||
|
"diskSize": <Google Cloud compute disk size>,
|
||||||
|
"replicas": <Node Replica Number for the cluster>,
|
||||||
|
"kubeconfig": "<Kubeconfig filename, Default is 'kubeconfig'>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</JSON>
|
||||||
|
|
||||||
|
The expected location for the GCP bootstrap configuration file is dictated by the "volume" mount
|
||||||
|
specified in the Airship config file (bootstrapInfo.ephemeral.container.volume).
|
||||||
|
For example, /home/esidshi/.airship folder as shown in the snippet below:
|
||||||
|
|
||||||
|
<Snippet>
|
||||||
|
apiVersion: airshipit.org/v1alpha1
|
||||||
|
bootstrapInfo:
|
||||||
|
ephemeral:
|
||||||
|
container:
|
||||||
|
volume: /home/esidshi/.airship:/kube
|
||||||
|
</Snippet>
|
3
bootstrap_capg/bootstrap-env.list
Normal file
3
bootstrap_capg/bootstrap-env.list
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
BOOTSTRAP_COMMAND=help
|
||||||
|
BOOTSTRAP_CONFIG=gcp-config.yaml
|
||||||
|
BOOTSTRAP_VOLUME=/tmp:/kube
|
101
bootstrap_capg/config/gcp_cluster.go
Normal file
101
bootstrap_capg/config/gcp_cluster.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bootstrapHelpFile = "help.txt"
|
||||||
|
|
||||||
|
// BootstrapCommand environment variable
|
||||||
|
bootstrapHome = "HOME"
|
||||||
|
bootstrapCommand = "BOOTSTRAP_COMMAND"
|
||||||
|
bootstrapConfig = "BOOTSTRAP_CONFIG"
|
||||||
|
bootstrapVolume = "BOOTSTRAP_VOLUME"
|
||||||
|
bootstrapVolumeSep = ":"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetVolumeMountPoints extracts the source and destination of a volume mount
|
||||||
|
func GetVolumeMountPoints(volumeMount string) (string, string) {
|
||||||
|
sepPos := strings.Index(volumeMount, bootstrapVolumeSep)
|
||||||
|
|
||||||
|
srcMountPoint := volumeMount[:sepPos]
|
||||||
|
dstMountPoint := volumeMount[sepPos+1:]
|
||||||
|
|
||||||
|
return srcMountPoint, dstMountPoint
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute - is used to invoke gcloud CLI commands
|
||||||
|
func execute(command string, arg ...string) error {
|
||||||
|
cmd := exec.Command(command, arg...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
log.Printf("Error executing script %s\n", command)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
log.Printf("Error waiting for command execution: %s", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateGKECluster creates the GKE cluster
|
||||||
|
func CreateGKECluster(gcpConfig *GcpConfig) error {
|
||||||
|
return prepareGCPCluster(gcpConfig, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteGKECluster deletes the GKE cluster
|
||||||
|
func DeleteGKECluster(gcpConfig *GcpConfig) error {
|
||||||
|
return prepareGCPCluster(gcpConfig, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelpGKECluster returns the help.txt for the GKE cluster
|
||||||
|
func HelpGKECluster() error {
|
||||||
|
homeDir := os.Getenv(bootstrapHome)
|
||||||
|
src := homeDir + "/" + bootstrapHelpFile
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not open %s file\n", src)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
_, dstMountPoint := GetVolumeMountPoints(os.Getenv(bootstrapVolume))
|
||||||
|
dst := dstMountPoint + "/" + bootstrapHelpFile
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not create %s file\n", dst)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to copy %s file to %s\n", src, dst)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return out.Close()
|
||||||
|
}
|
117
bootstrap_capg/config/gcp_config.go
Normal file
117
bootstrap_capg/config/gcp_config.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes 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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"gopkg.in/go-playground/validator.v9"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GcpConfig holds configurations for bootstrap steps
|
||||||
|
type GcpConfig struct {
|
||||||
|
// +optional
|
||||||
|
Kind string `yaml:"kind" validate:"required"`
|
||||||
|
|
||||||
|
// +optional
|
||||||
|
APIVersion string `yaml:"apiVersion" validate:"required"`
|
||||||
|
|
||||||
|
// Configuration parameters for metadata
|
||||||
|
Metadata *Metadata `yaml:"metadata" validate:"required"`
|
||||||
|
|
||||||
|
// Configuration parameters for metadata
|
||||||
|
Credentials *Credentials `yaml:"credentials" validate:"required"`
|
||||||
|
|
||||||
|
// Configuration parameters for spec
|
||||||
|
Spec *Spec `yaml:"spec"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metadata structure provides the cluster name to assign and labels to the k8s cluster
|
||||||
|
type Metadata struct {
|
||||||
|
Name string `yaml:"name" validate:"required"`
|
||||||
|
Labels []string `yaml:"labels,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credentials structu provides the credentials to authenticate with Azure Cloud
|
||||||
|
type Credentials struct {
|
||||||
|
Project string `yaml:"project" validate:"required"`
|
||||||
|
Account string `yaml:"account" validate:"required"`
|
||||||
|
Credential string `yaml:"credential" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spec structure contains the info for the ck8s luster to deploy
|
||||||
|
type Spec struct {
|
||||||
|
Region string `yaml:"region,omitempty"`
|
||||||
|
Zone string `yaml:"zone,omitempty"`
|
||||||
|
Cluster Cluster `yaml:"cluster"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cluster struct provides data for the k8s cluster to deploy
|
||||||
|
type Cluster struct {
|
||||||
|
// Kubernetes version to deploy
|
||||||
|
K8SVersion string `yaml:"k8sVersion,omitempty"`
|
||||||
|
|
||||||
|
// Google Cloud Compote VM size to use for the cluster
|
||||||
|
MachineSize string `yaml:"machineSize,omitempty"`
|
||||||
|
|
||||||
|
// Google Cloud Compote disk size to use for the cluster
|
||||||
|
DiskSize uint8 `yaml:"diskSize,omitempty" validate:"gte=1"`
|
||||||
|
|
||||||
|
// Number of nodes to deploy for the cluster
|
||||||
|
Replicas uint8 `yaml:"replicas,omitempty" validate:"gte=1,lte=100"`
|
||||||
|
|
||||||
|
// Kubeconfig filename to save
|
||||||
|
Kubeconfig string `yaml:"kubeconfig,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadYAMLFile reads YAML-formatted configuration file and
|
||||||
|
// de-serializes it to a given object
|
||||||
|
func ReadYAMLFile(filePath string, cfg *GcpConfig) error {
|
||||||
|
data, err := ioutil.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to read GCP Ephemeral configuration file: err #%v ", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return yaml.Unmarshal(data, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateConfigFile validates GCP configuration file for the Ephemeral Cluster
|
||||||
|
func ValidateConfigFile(config *GcpConfig) error {
|
||||||
|
validate := validator.New()
|
||||||
|
err := validate.Struct(config)
|
||||||
|
if err != nil {
|
||||||
|
var invalidError *validator.InvalidValidationError
|
||||||
|
if errors.As(err, &invalidError) {
|
||||||
|
log.Println(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Ephemeral cluster configuration file validation failed")
|
||||||
|
for _, err := range err.(validator.ValidationErrors) {
|
||||||
|
log.Printf(" Namespace = %s\n", err.Namespace())
|
||||||
|
log.Printf(" Tag = %s\n", err.Tag())
|
||||||
|
log.Printf(" Type = %s\n", err.Type())
|
||||||
|
log.Printf(" Value = %s\n", err.Value())
|
||||||
|
log.Printf(" Param = %s\n\n", err.Param())
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
159
bootstrap_capg/config/gke_cluster.go
Normal file
159
bootstrap_capg/config/gke_cluster.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
gcloud = "gcloud"
|
||||||
|
auth = "auth"
|
||||||
|
activateServiceAccount = "activate-service-account"
|
||||||
|
config = "config"
|
||||||
|
set = "set"
|
||||||
|
project = "project"
|
||||||
|
container = "container"
|
||||||
|
clusters = "clusters"
|
||||||
|
create = "create"
|
||||||
|
delete = "delete"
|
||||||
|
getCredentials = "get-credentials"
|
||||||
|
|
||||||
|
zoneP = "--zone"
|
||||||
|
nodeLocations = "--node-locations"
|
||||||
|
keyFileP = "--key-file"
|
||||||
|
clusterVersionP = "--cluster-version"
|
||||||
|
machineTypeP = "--machine-type"
|
||||||
|
numNodesP = "--num-nodes"
|
||||||
|
enableIPAlias = "--enable-ip-alias"
|
||||||
|
imageTypeP = "--image-type"
|
||||||
|
diskTypeP = "--disk-type"
|
||||||
|
diskSizeP = "--disk-size"
|
||||||
|
metadataP = "--metadata"
|
||||||
|
scopesP = "--scopes"
|
||||||
|
enableStackDriverKubernetesP = "--enable-stackdriver-kubernetes"
|
||||||
|
enableAutoUpgradeP = "--enable-autoupgrade"
|
||||||
|
enableAutoRepairP = "--enable-autorepair"
|
||||||
|
quietP = "--quiet"
|
||||||
|
|
||||||
|
defaultClusterName = "capi-gcp"
|
||||||
|
defaultRegion = "us-central1"
|
||||||
|
defaultZone = "us-central1-c"
|
||||||
|
defaultMachineSize = "e2-medium"
|
||||||
|
defaultK8SVersion = "1.16.13-gke.401"
|
||||||
|
defaultKubeconfig = "kubeconfig"
|
||||||
|
|
||||||
|
kubeconfigVar = "KUBECONFIG"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultGCPConfig verify if any optional config data is missing.
|
||||||
|
func defaultGCPConfig(gcpConfig *GcpConfig) error {
|
||||||
|
if gcpConfig.Spec.Region == "" {
|
||||||
|
gcpConfig.Spec.Region = defaultRegion
|
||||||
|
}
|
||||||
|
if gcpConfig.Spec.Zone == "" {
|
||||||
|
gcpConfig.Spec.Zone = defaultZone
|
||||||
|
}
|
||||||
|
if gcpConfig.Metadata.Name == "" {
|
||||||
|
gcpConfig.Metadata.Name = defaultClusterName
|
||||||
|
}
|
||||||
|
if gcpConfig.Spec.Cluster.MachineSize == "" {
|
||||||
|
gcpConfig.Spec.Cluster.MachineSize = defaultMachineSize
|
||||||
|
}
|
||||||
|
if gcpConfig.Spec.Cluster.K8SVersion == "" {
|
||||||
|
gcpConfig.Spec.Cluster.K8SVersion = defaultK8SVersion
|
||||||
|
}
|
||||||
|
if gcpConfig.Spec.Cluster.Kubeconfig == "" {
|
||||||
|
gcpConfig.Spec.Cluster.Kubeconfig = defaultKubeconfig
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepareGCPCluster logs in, create resource group, etc
|
||||||
|
func prepareGCPCluster(gcpConfig *GcpConfig, isCreate bool) error {
|
||||||
|
// Verify if Google Cloud config file provides all information needed for creating a cluster
|
||||||
|
err := defaultGCPConfig(gcpConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Kubeconfig filename
|
||||||
|
volMount := os.Getenv("BOOTSTRAP_VOLUME")
|
||||||
|
_, dstMount := GetVolumeMountPoints(volMount)
|
||||||
|
|
||||||
|
gcpProject := gcpConfig.Credentials.Project
|
||||||
|
gcpAccount := gcpConfig.Credentials.Account
|
||||||
|
gcpCredential := dstMount + "/" + gcpConfig.Credentials.Credential
|
||||||
|
|
||||||
|
clusterName := gcpConfig.Metadata.Name
|
||||||
|
region := gcpConfig.Spec.Region
|
||||||
|
zone := gcpConfig.Spec.Zone
|
||||||
|
|
||||||
|
machineSize := gcpConfig.Spec.Cluster.MachineSize
|
||||||
|
nodeCount := strconv.FormatInt(int64(gcpConfig.Spec.Cluster.Replicas), 10)
|
||||||
|
k8sVersion := gcpConfig.Spec.Cluster.K8SVersion
|
||||||
|
kubeconfigFile := gcpConfig.Spec.Cluster.Kubeconfig
|
||||||
|
|
||||||
|
// login to GCP account using Service Principal
|
||||||
|
err = execute(gcloud, auth, activateServiceAccount, gcpAccount, keyFileP, gcpCredential)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to login into GCP using Service Account\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set project to use to the configuration
|
||||||
|
err = execute(gcloud, config, set, project, gcpProject)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to set GCP project to the configuration\n")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isCreate {
|
||||||
|
// Creating Google GKE cluster
|
||||||
|
err = execute(gcloud, container, clusters, create, clusterName,
|
||||||
|
zoneP, zone, nodeLocations, zone,
|
||||||
|
numNodesP, nodeCount, machineTypeP, machineSize,
|
||||||
|
enableIPAlias, enableAutoUpgradeP, enableAutoRepairP,
|
||||||
|
clusterVersionP, k8sVersion)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to create GKE cluster %s in %s region.\n", clusterName, region)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieving the Kubeconfig file for the cluster
|
||||||
|
dstKubeconfig := dstMount + "/" + kubeconfigFile
|
||||||
|
os.Setenv(kubeconfigVar, dstKubeconfig)
|
||||||
|
err = execute(gcloud, container, clusters, getCredentials, clusterName, zoneP, zone)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to retrieve kubeconfig file for GCP cluster %s in %s region.\n", clusterName, region)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(dstKubeconfig); err != nil {
|
||||||
|
log.Printf("Failed to retrieve kubeconfig file for GCP cluster %s in %s region.\n", clusterName, region)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Delete GCP GKE cluster
|
||||||
|
err = execute(gcloud, container, clusters, delete, clusterName, zoneP, zone, quietP)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to delete GKE cluster %s in %s region.\n", clusterName, region)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
23
bootstrap_capg/gcp-config.json
Normal file
23
bootstrap_capg/gcp-config.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "GoogleCloudConfig",
|
||||||
|
"metadata": {
|
||||||
|
"name": "capi-google-zuul"
|
||||||
|
},
|
||||||
|
"credentials": {
|
||||||
|
"project": "peak-vista-274815",
|
||||||
|
"account": "airship-kubernetes-account@peak-vista-274815.iam.gserviceaccount.com",
|
||||||
|
"credential": "gcp-credentials.json"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"region": "us-central1",
|
||||||
|
"zone": "us-central1-c",
|
||||||
|
"cluster": {
|
||||||
|
"k8sVersion": "1.16.13-gke.1",
|
||||||
|
"machineSize": "e2-medium",
|
||||||
|
"diskSize": 100,
|
||||||
|
"replicas": 1,
|
||||||
|
"kubeconfig": "capg.kubeconfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
29
bootstrap_capg/gcp-config.yaml
Normal file
29
bootstrap_capg/gcp-config.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
apiVersion: v1
|
||||||
|
kind: GoogleCloudConfig
|
||||||
|
metadata:
|
||||||
|
name: capi-google-zuul
|
||||||
|
credentials:
|
||||||
|
project: <GCP Project ID>
|
||||||
|
account: <GCP Account ID>
|
||||||
|
credential: gcp-credentials.json
|
||||||
|
spec:
|
||||||
|
region: us-central1
|
||||||
|
zone: us-central1-c
|
||||||
|
cluster:
|
||||||
|
k8sVersion: 1.16.13-gke.401
|
||||||
|
machineSize: e2-medium
|
||||||
|
diskSize: 100
|
||||||
|
replicas: 1
|
||||||
|
kubeconfig: capg.kubeconfig
|
12
bootstrap_capg/gcp-credentials.json
Normal file
12
bootstrap_capg/gcp-credentials.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"type": "service_account",
|
||||||
|
"project_id": "<your project ID>. NOTE: This file was generated by Google Cloud",
|
||||||
|
"private_key_id": "<your project key ID>",
|
||||||
|
"private_key": "-----BEGIN PRIVATE KEY-----<enter your private key here>-----END PRIVATE KEY-----\n",
|
||||||
|
"client_email": "<your account email>",
|
||||||
|
"client_id": "<your client id>",
|
||||||
|
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
"token_uri": "https://oauth2.googleapis.com/token",
|
||||||
|
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||||
|
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/airship-kubernetes-account%40peak-vista-274815.iam.gserviceaccount.com"
|
||||||
|
}
|
78
bootstrap_capg/main.go
Normal file
78
bootstrap_capg/main.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
|
||||||
|
https://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 main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"opendev.org/airship/images/bootstrap_capg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
createCmd = "create"
|
||||||
|
deleteCmd = "delete"
|
||||||
|
helpCmd = "help"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var configPath string
|
||||||
|
|
||||||
|
flag.StringVar(&configPath, "c", "", "Path for the Google Cloud bootstrap configuration (yaml) file")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if configPath == "" {
|
||||||
|
volMount := os.Getenv("BOOTSTRAP_VOLUME")
|
||||||
|
_, dstMount := config.GetVolumeMountPoints(volMount)
|
||||||
|
gcpConfigPath := dstMount + "/" + os.Getenv("BOOTSTRAP_CONFIG")
|
||||||
|
configPath = gcpConfigPath
|
||||||
|
}
|
||||||
|
|
||||||
|
configYAML := &config.GcpConfig{}
|
||||||
|
err := config.ReadYAMLFile(configPath, configYAML)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to load Google Cloud Bootstrap config file")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = config.ValidateConfigFile(configYAML)
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
command := os.Getenv("BOOTSTRAP_COMMAND")
|
||||||
|
switch {
|
||||||
|
case command == createCmd:
|
||||||
|
err = config.CreateGKECluster(configYAML)
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(5)
|
||||||
|
}
|
||||||
|
case command == deleteCmd:
|
||||||
|
err = config.DeleteGKECluster(configYAML)
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(6)
|
||||||
|
}
|
||||||
|
case command == helpCmd:
|
||||||
|
err = config.HelpGKECluster()
|
||||||
|
if err != nil {
|
||||||
|
os.Exit(7)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Printf("The --command parameter value shall be 'create', 'delete' or 'help'")
|
||||||
|
os.Exit(8)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user