|
|
|
@ -20,15 +20,21 @@ import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"log"
|
|
|
|
|
"reflect"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
|
|
|
"k8s.io/kubernetes/pkg/api/errors"
|
|
|
|
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
|
|
|
|
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
|
|
|
|
"k8s.io/kubernetes/pkg/apis/batch"
|
|
|
|
|
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
|
|
|
|
"k8s.io/kubernetes/pkg/kubectl"
|
|
|
|
|
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
|
|
|
|
"k8s.io/kubernetes/pkg/kubectl/resource"
|
|
|
|
|
"k8s.io/kubernetes/pkg/runtime"
|
|
|
|
|
"k8s.io/kubernetes/pkg/util/strategicpatch"
|
|
|
|
|
"k8s.io/kubernetes/pkg/util/yaml"
|
|
|
|
|
"k8s.io/kubernetes/pkg/watch"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
@ -57,6 +63,77 @@ func (c *Client) Create(namespace string, reader io.Reader) error {
|
|
|
|
|
return perform(c, namespace, reader, createResource)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update reads in the current configuration and a modified configuration from io.reader
|
|
|
|
|
// and creates resources that don't already exists, updates resources that have been modified
|
|
|
|
|
// and deletes resources from the current configuration that are not present in the
|
|
|
|
|
// modified configuration
|
|
|
|
|
//
|
|
|
|
|
// Namespace will set the namespaces
|
|
|
|
|
func (c *Client) Update(namespace string, currentReader, modifiedReader io.Reader) error {
|
|
|
|
|
current := c.NewBuilder(includeThirdPartyAPIs).
|
|
|
|
|
ContinueOnError().
|
|
|
|
|
NamespaceParam(namespace).
|
|
|
|
|
DefaultNamespace().
|
|
|
|
|
Stream(currentReader, "").
|
|
|
|
|
Flatten().
|
|
|
|
|
Do()
|
|
|
|
|
|
|
|
|
|
modified := c.NewBuilder(includeThirdPartyAPIs).
|
|
|
|
|
ContinueOnError().
|
|
|
|
|
NamespaceParam(namespace).
|
|
|
|
|
DefaultNamespace().
|
|
|
|
|
Stream(modifiedReader, "").
|
|
|
|
|
Flatten().
|
|
|
|
|
Do()
|
|
|
|
|
|
|
|
|
|
currentInfos, err := current.Infos()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
modifiedInfos := []*resource.Info{}
|
|
|
|
|
|
|
|
|
|
modified.Visit(func(info *resource.Info, err error) error {
|
|
|
|
|
modifiedInfos = append(modifiedInfos, info)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
resourceName := info.Name
|
|
|
|
|
|
|
|
|
|
helper := resource.NewHelper(info.Client, info.Mapping)
|
|
|
|
|
if _, err := helper.Get(info.Namespace, resourceName, info.Export); err != nil {
|
|
|
|
|
if !errors.IsNotFound(err) {
|
|
|
|
|
return fmt.Errorf("Could not get information about the resource: err: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Since the resource does not exist, create it.
|
|
|
|
|
if err := createResource(info); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
kind := info.Mapping.GroupVersionKind.Kind
|
|
|
|
|
log.Printf("Created a new %s called %s\n", kind, resourceName)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentObj, err := getCurrentObject(resourceName, currentInfos)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := updateResource(info, currentObj); err != nil {
|
|
|
|
|
log.Printf("error updating the resource %s:\n\t %v", resourceName, err)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
deleteUnwantedResources(currentInfos, modifiedInfos)
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Delete deletes kubernetes resources from an io.reader
|
|
|
|
|
//
|
|
|
|
|
// Namespace will set the namespace
|
|
|
|
@ -136,6 +213,51 @@ func createResource(info *resource.Info) error {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func deleteResource(info *resource.Info) error {
|
|
|
|
|
return resource.NewHelper(info.Client, info.Mapping).Delete(info.Namespace, info.Name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updateResource(modified *resource.Info, currentObj runtime.Object) error {
|
|
|
|
|
|
|
|
|
|
encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...)
|
|
|
|
|
originalSerialization, err := runtime.Encode(encoder, currentObj)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
editedSerialization, err := runtime.Encode(encoder, modified.Object)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
originalJS, err := yaml.ToJSON(originalSerialization)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
editedJS, err := yaml.ToJSON(editedSerialization)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if reflect.DeepEqual(originalJS, editedJS) {
|
|
|
|
|
return fmt.Errorf("Looks like there are no changes for %s", modified.Name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
patch, err := strategicpatch.CreateStrategicMergePatch(originalJS, editedJS, currentObj)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// send patch to server
|
|
|
|
|
helper := resource.NewHelper(modified.Client, modified.Mapping)
|
|
|
|
|
if _, err = helper.Patch(modified.Namespace, modified.Name, api.StrategicMergePatchType, patch); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func watchUntilReady(info *resource.Info) error {
|
|
|
|
|
w, err := resource.NewHelper(info.Client, info.Mapping).WatchSingle(info.Namespace, info.Name, info.ResourceVersion)
|
|
|
|
|
if err != nil {
|
|
|
|
@ -213,3 +335,37 @@ func (c *Client) ensureNamespace(namespace string) error {
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func deleteUnwantedResources(currentInfos, modifiedInfos []*resource.Info) {
|
|
|
|
|
for _, cInfo := range currentInfos {
|
|
|
|
|
found := false
|
|
|
|
|
for _, m := range modifiedInfos {
|
|
|
|
|
if m.Name == cInfo.Name {
|
|
|
|
|
found = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if !found {
|
|
|
|
|
log.Printf("Deleting %s...", cInfo.Name)
|
|
|
|
|
if err := deleteResource(cInfo); err != nil {
|
|
|
|
|
log.Printf("Failed to delete %s, err: %s", cInfo.Name, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getCurrentObject(targetName string, infos []*resource.Info) (runtime.Object, error) {
|
|
|
|
|
var curr *resource.Info
|
|
|
|
|
for _, currInfo := range infos {
|
|
|
|
|
if currInfo.Name == targetName {
|
|
|
|
|
curr = currInfo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if curr == nil {
|
|
|
|
|
return nil, fmt.Errorf("No resource with the name %s found.", targetName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
encoder := api.Codecs.LegacyCodec(registered.EnabledVersions()...)
|
|
|
|
|
defaultVersion := unversioned.GroupVersion{}
|
|
|
|
|
return resource.AsVersionedObject([]*resource.Info{curr}, false, defaultVersion, encoder)
|
|
|
|
|
}
|
|
|
|
|