Files
2020-02-17 15:39:55 -05:00

193 lines
6.0 KiB
Go

package main
import (
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"path/filepath"
"strings"
"time"
appV1 "k8s.io/api/apps/v1"
coreV1 "k8s.io/api/core/v1"
v1BetaV1 "k8s.io/api/extensions/v1beta1"
kubeErrors "k8s.io/apimachinery/pkg/api/errors"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
// CreateOrUpdateDeployment -- Checks if deployment already exists, updates if it does, creates if it doesn't
func CreateOrUpdateDeployment(clientset *kubernetes.Clientset, namespace string, deployment *appV1.Deployment) error {
deploymentExists, err := deploymentExists(clientset, namespace, deployment.Name)
if err != nil {
return err
}
if deploymentExists {
log.Printf("📦 Found existing deployment '%s'. Updating.", deployment.Name)
_, err = clientset.AppsV1().Deployments(namespace).Update(deployment)
return err
}
log.Printf("📦 Creating new deployment '%s'. Updating.", deployment.Name)
_, err = clientset.AppsV1().Deployments(namespace).Create(deployment)
return err
}
// deploymentExists -- Updates given deployment in Kubernetes
func deploymentExists(clientset *kubernetes.Clientset, namespace string, name string) (bool, error) {
_, err := clientset.AppsV1().Deployments(namespace).Get(name, metaV1.GetOptions{})
if err != nil {
// TODO: Only conver to StatusError if the error is in fact a status error
statusError, ok := err.(*kubeErrors.StatusError)
if ok && statusError.Status().Code == http.StatusNotFound {
return false, nil
}
return false, err
}
return true, nil
}
const (
stateUpdated = "📦 Updated"
stateFailed = "⛔️ Failed"
)
// waitUntilDeploymentSettled -- Waits until ready, failure or timeout
func waitUntilDeploymentSettled(clientset *kubernetes.Clientset, namespace string, name string, timeoutInSeconds int64) (string, error) {
fieldSelector := strings.Join([]string{"metadata.name", name}, "=")
watchOptions := metaV1.ListOptions{
FieldSelector: fieldSelector,
Watch: true,
}
watcher, err := clientset.AppsV1().Deployments(namespace).Watch(watchOptions)
if err != nil {
return stateFailed, fmt.Errorf("watch deployment; %w", err)
}
liveDeployment, err := clientset.AppsV1().Deployments(namespace).Get(name, metaV1.GetOptions{})
if err != nil {
return stateFailed, fmt.Errorf("get deployment; %w", err)
}
log.Printf("📦 Unavailable replicas: %d", liveDeployment.Status.UnavailableReplicas)
if liveDeployment.Status.UnavailableReplicas == 0 {
return "📦 Updated", nil
}
timer := time.NewTimer(time.Duration(timeoutInSeconds) * time.Second)
for {
select {
case <-timer.C:
return stateFailed, errors.New("deployment watcher timed out. Something is wrong")
case event := <-watcher.ResultChan():
deployment := event.Object.(*appV1.Deployment)
if deployment.Status.UnavailableReplicas == 0 {
return stateUpdated, nil
}
log.Printf("📦 Unavailable replicas: %d", deployment.Status.UnavailableReplicas)
}
}
}
// ApplyService creates a service if it doesn't exists, updates it if it does
func ApplyService(clientset *kubernetes.Clientset, namespace string, service *coreV1.Service) error {
existingService, exists, err := getService(clientset, namespace, service.Name)
if err != nil {
return err
}
if exists {
_, err = clientset.CoreV1().Services(namespace).Update(existingService)
return err
}
_, err = clientset.CoreV1().Services(namespace).Create(service)
return err
}
// getService returns a service object, or false if it doesn't exist
func getService(clientset *kubernetes.Clientset, namespace string, name string) (*coreV1.Service, bool, error) {
service, err := clientset.CoreV1().Services(namespace).Get(name, metaV1.GetOptions{})
if err != nil {
statusError, ok := err.(*kubeErrors.StatusError)
if ok && statusError.Status().Code == http.StatusNotFound {
return nil, false, nil
}
return nil, false, err
}
return service, true, nil
}
// ApplyConfigMapFromFile -- Updates given deployment in Kubernetes
func ApplyConfigMapFromFile(clientset *kubernetes.Clientset, namespace string, configMap *coreV1.ConfigMap, path string) error {
log.Printf("📦 Reading contents of %s", path)
_, filename := filepath.Split(path)
fileContents, err := ioutil.ReadFile(path)
if err != nil {
return err
}
configMapData := make(map[string]string)
configMapData[filename] = string(fileContents)
configMap.Data = configMapData
// Check if deployment exists
exists, err := configMapExists(clientset, namespace, configMap.Name)
if err != nil {
return err
}
if exists {
log.Printf("📦 Found existing deployment. Updating %s.", configMap.Name)
_, err = clientset.CoreV1().ConfigMaps(namespace).Update(configMap)
return err
}
_, err = clientset.CoreV1().ConfigMaps(namespace).Create(configMap)
return err
}
// configMapExists -- Updates given deployment in Kubernetes
func configMapExists(clientset *kubernetes.Clientset, namespace string, name string) (bool, error) {
_, err := clientset.CoreV1().ConfigMaps(namespace).Get(name, metaV1.GetOptions{})
if err != nil {
statusError, ok := err.(*kubeErrors.StatusError)
if ok && statusError.Status().Code == http.StatusNotFound {
return false, nil
}
return false, err
}
return true, nil
}
func ApplyIngress(clientset *kubernetes.Clientset, namespace string, ingress *v1BetaV1.Ingress) error {
_, exists, err := getIngress(clientset, namespace, ingress.Name)
if err != nil {
return err
}
if !exists {
_, err = clientset.ExtensionsV1beta1().Ingresses(namespace).Create(ingress)
return err
}
_, err = clientset.ExtensionsV1beta1().Ingresses(namespace).Update(ingress)
return err
}
func getIngress(clientset *kubernetes.Clientset, namespace string, name string) (*v1BetaV1.Ingress, bool, error) {
ingress, err := clientset.ExtensionsV1beta1().Ingresses(namespace).Get(name, metaV1.GetOptions{})
if err != nil {
statusError, ok := err.(*kubeErrors.StatusError)
if ok && statusError.Status().Code == http.StatusNotFound {
return nil, false, nil
}
return nil, false, err
}
return ingress, true, nil
}