GithubHelp home page GithubHelp logo

jimoosciuc / manifestival Goto Github PK

View Code? Open in Web Editor NEW

This project forked from manifestival/manifestival

0.0 2.0 0.0 248 KB

Create, manipulate, apply and delete Kubernetes resource manifests at runtime

License: Apache License 2.0

Go 100.00%

manifestival's Introduction

Manifestival

Build Status

Manifestival is a library for manipulating a set of unstructured Kubernetes resources. Essentially, it enables you to toss a "bag of YAML" at a k8s cluster.

It's sort of like embedding a simplified kubectl in your Go application. You can load a manifest of resources from a variety of sources, optionally transform/filter those resources, and then apply/delete them to/from your k8s cluster.

See CHANGELOG.md

Creating Manifests

Manifests may be constructed from a variety of sources. Once created, they are immutable, but new instances may be created from them using their Append, Filter and Transform functions.

The typical way to create a manifest is by calling NewManifest

manifest, err := NewManifest("/path/to/file.yaml")

But NewManifest is just a convenience function that calls ManifestFrom with a Path, an implementation of Source.

Sources

A manifest is created by passing an implementation of the Source interface to the ManifestFrom function. Here are the built-in types that implement Source:

  • Path
  • Recursive
  • Slice
  • Reader

The Path source is the most versatile. It's a string representing the location of some YAML content in many possible forms: a file, a directory of files, a URL, or a comma-delimited list of any of those things, all of which will be combined into a single manifest.

// Single file
m, err := ManifestFrom(Path("/path/to/file.yaml"))

// All files in a directory
m, err := ManifestFrom(Path("/path/to/dir"))

// A remote URL
m, err := ManifestFrom(Path("http://site.com/manifest.yaml"))

// All of the above
m, err := ManifestFrom(Path("/path/to/file.yaml,/path/to/dir,http://site.com/manifest.yaml"))

Recursive works exactly like Path except that directories are searched recursively.

The Slice source enables the creation of a manifest from an existing slice of []unstructured.Unstructured. This is helpful for testing and, combined with the Resources accessor, facilitates more sophisticated combinations of manifests, e.g. a crude form of Append:

m3, _ := ManifestFrom(Slice(append(m1.Resources(), m2.Resources()...)))

And Reader is a function that takes an io.Reader and returns a Source from which valid YAML is expected.

Append

The Append function enables the creation of new manifests from the concatenation of others. The resulting manifest retains the options, e.g. client and logger, of the receiver. For example,

core, _ := NewManifest(path, UseLogger(logger), UseClient(client))
istio, _ := NewManifest(pathToIstio)
kafka, _ := NewManifest(pathToKafka)

manifest := core.Append(istio, kafka)

Filter

Filter returns a new Manifest containing only the resources for which all passed predicates return true. A Predicate is a function that takes an Unstructured resource and returns a bool indicating whether the resource should be included in the filtered results.

There are a few built-in predicates and some helper functions for creating your own:

  • All returns a Predicate that returns true unless any of its arguments returns false
  • Everything is equivalent to All()
  • Any returns a Predicate that returns false unless any of its arguments returns true
  • Nothing is equivalent to Any()
  • Not negates its argument, returning false if its argument returns true
  • ByName, ByKind, ByLabel, ByAnnotation, and ByGVK filter resources by their respective attributes.
  • CRDs and its complement NoCRDs are handy filters for CustomResourceDefinitions
  • In can be used to find the "intersection" of two manifests
clusterRBAC := Any(ByKind("ClusterRole"), ByKind("ClusterRoleBinding"))
namespaceRBAC := Any(ByKind("Role"), ByKind("RoleBinding"))
rbac := Any(clusterRBAC, namespaceRBAC)

theRBAC := manifest.Filter(rbac)
theRest := manifest.Filter(Not(rbac))

// Find all resources named "controller" w/label 'foo=bar' that aren't CRD's
m := manifest.Filter(ByLabel("foo", "bar"), ByName("controller"), NoCRDs)

Because the Predicate receives the resource by reference, any changes you make to it will be reflected in the returned Manifest, but not in the one being filtered -- manifests are immutable. Since errors are not in the Predicate interface, you should limit changes to those that won't error. For more complex mutations, use Transform instead.

Transform

Transform will apply some function to every resource in your manifest, and return a new Manifest with the results. It's common for a Transformer function to include a guard that simply returns if the unstructured resource isn't of interest.

There are a few useful transformers provided, including InjectNamespace and InjectOwner. An example should help to clarify:

func updateDeployment(resource *unstructured.Unstructured) error {
    if resource.GetKind() != "Deployment" {
        return nil
    }
    // Either manipulate the Unstructured resource directly or...
    // convert it to a structured type...
    var deployment = &appsv1.Deployment{}
    if err := scheme.Scheme.Convert(resource, deployment, nil); err != nil {
        return err
    }

    // Now update the deployment!
    
    // If you converted it, convert it back, otherwise return nil
    return scheme.Scheme.Convert(deployment, resource, nil)
}

m, err := manifest.Transform(updateDeployment, InjectOwner(parent), InjectNamespace("foo"))

Applying Manifests

Persisting manifests is accomplished via the Apply and Delete functions of the Manifestival interface, and though DryRun doesn't change anything, it does query the API Server. Therefore all of these functions require a Client.

Client

Manifests require a Client implementation to interact with a k8s API server. There are currently two alternatives:

To apply your manifest, you'll need to provide a client when you create it with the UseClient functional option, like so:

manifest, err := NewManifest("/path/to/file.yaml", UseClient(client))
if err != nil {
    panic("Failed to load manifest")
}

It's the Client that enables you to persist the resources in your manifest using Apply, remove them using Delete, compute differences using DryRun, and occasionally it's even helpful to invoke the manifest's Client directly...

manifest.Apply()
manifest.Filter(NoCRDs).Delete()

u := manifest.Resources()[0]
u.SetName("foo")
manifest.Client.Create(&u)

fake.Client

The fake package includes a fake Client with stubs you can easily override in your unit tests. For example,

func verifySomething(t *testing.T, expected *unstructured.Unstructured) {
    client := fake.Client{
        fake.Stubs{
            Create: func(u *unstructured.Unstructured) error {
                if !reflect.DeepEqual(u, expected) {
                    t.Error("You did it wrong!")
                }
                return nil
            },
        },
    }
    manifest, _ := NewManifest("testdata/some.yaml", UseClient(client))
    callSomethingThatUltimatelyAppliesThis(manifest)
}

There is also a convenient New function that returns a fully-functioning fake Client by overriding the stubs to persist the resources in a map. Here's an example using it to test the DryRun function:

client := fake.New()
current, _ := NewManifest("testdata/current.yaml", UseClient(client))
current.Apply()
modified, _ := NewManifest("testdata/modified.yaml", UseClient(client))
diffs, err := modified.DryRun()

Logging

By default, Manifestival logs nothing, but it will happily log its actions if you pass it a logr.Logger via its UseLogger functional option, like so:

m, _ := NewManifest(path, UseLogger(log.WithName("manifestival")), UseClient(c))

Apply

Apply will persist every resource in the manifest to the cluster. It will invoke either Create or Update depending on whether the resource already exists. And if it does exist, the same 3-way strategic merge patch used by kubectl apply will be applied. And the same annotation used by kubectl to record the resource's previous configuration will be updated, too.

The following functional options are supported, all of which map to either the k8s metav1.CreateOptions and metav1.UpdateOptions fields or kubectl apply flags:

  • Overwrite [true] resolve any conflicts in favor of the manifest
  • FieldManager the name of the actor applying changes
  • DryRunAll if present, changes won't persist

Delete

Delete attempts to delete all the manifest's resources in reverse order. Depending on the resources' owner references, race conditions with the k8s garbage collector may occur, and by default NotFound errors are silently ignored.

The following functional options are supported, all except IgnoreNotFound mirror the k8s metav1.DeleteOptions:

  • IgnoreNotFound [true] silently ignore any NotFound errors
  • GracePeriodSeconds the number of seconds before the object should be deleted
  • Preconditions must be fulfilled before a deletion is carried out
  • PropagationPolicy whether and how garbage collection will be performed

DryRun

DryRun returns a list of JSON merge patches that show the effects of applying the manifest without modifying the live system. Each item in the returned list is valid content for the kubectl patch command.

manifestival's People

Contributors

jcrossley3 avatar evankanderson avatar markusthoemmes avatar aliok avatar cardil avatar nak3 avatar nikhil-thomas avatar vincent-pli avatar jimoosciuc avatar

Watchers

James Cloos avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.