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:
Sidney Shiba 2020-09-16 14:26:53 -05:00
parent f45d429c3c
commit aa624ce27c
13 changed files with 734 additions and 0 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
*build/
bootstrap_capg/.vscode/launch.json
bootstrap_capg/go.mod

56
bootstrap_capg/Dockerfile Normal file
View 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
View 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
View 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.

View 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>

View File

@ -0,0 +1,3 @@
BOOTSTRAP_COMMAND=help
BOOTSTRAP_CONFIG=gcp-config.yaml
BOOTSTRAP_VOLUME=/tmp:/kube

View 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()
}

View 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
}

View 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
}

View 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"
}
}
}

View 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

View 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
View 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)
}