WIP: if you would like to help out with the initial development to get this project off the ground please open an ISSUE to ask about what needs to be done or what you would like to work on.
Gradle plugin for working with Kubernetes.
CI | Codecov | Docs | Questions | Release |
---|---|---|---|---|
Learning from, and building upon, the work done, lessons learned, and features requested, with the gradle-docker-plugin we sought to create a plugin that was easy to use up front but with the proper hooks/constructs in place to allow for more flexible solutions and complicated scenarios should the developer want to take advantage of them. Things like (but not limited to):
- CRUD operations around all kubernetes endpoints (e.g. namespaces, services, pods, etc).
- Dependent libraries loaded into their own class-loader so as not to clobber
buildscript
classpath. - config{} provides a common means of configuring the backing object Tasks are based upon. Instead of exposing every possible property the backing
kubernetes-client
may provide we instead expose the most common ones and let the user further configure things through this construct should the need arise. - retry{} provides a common means of configuring retries for a given Task. The construct itself can be provided globally on the extension point, for all tasks to inherit, or upon each individual task for more granular use-cases.
- response() hands back to the user, once task execution has finished, the object given to us by the
kubernetes-client
execution. This allows downstream tasks to query a previously ran Task for its output and potentially design more complicated scenarios with it. - reactive-streams gives users a more dynamic experience when working with a give tasks life-cycle.
- More streamlined, simplifed, and documented codebase allowing for easier contributions from the community.
buildscript() {
repositories {
jcenter()
}
dependencies {
classpath group: 'com.bmuschko', name: 'gradle-kubernetes-plugin', version: 'X.Y.Z'
}
}
apply plugin: 'gradle-kubernetes-plugin'
The kubernetes
extension acts as a mapper to the Config object provided by the kubernetes-client library which we use in the backend. This allows you to configure this plugin in exactly the same way you would configure the java client.
kubernetes {
config {
withMasterUrl("https://mymaster.com")
}
}
All additional options that exist to configure the client are also honored here.
The below table(s) document each of our tasks and their respective features
in depth details of which are provided further below. Special care should be taken when using the config{}
construct as it's considered an ADVANCED feature and developers should favor using the OOTB inputs/properties of the task itself whenever possible vs configuring things directly on the backing kubernetes-client
object.
key table
Column | Description |
---|---|
Name | Name and hyperlink to Task source. |
config{} |
Object config{} closure maps to. |
onNext{} |
Object next iteration of onNext{} closure will receive. |
response() |
Object response() method returns AFTER task execution has finished. |
Name | config{} |
onNext{} |
response() |
---|---|---|---|
KubernetesClient | KubernetesClient | KubernetesClient | KubernetesClient |
Name | config{} |
onNext{} |
response() |
---|---|---|---|
ListDeployments | MixedOperation | Deployment | DeploymentList |
CreateDeployment | DoneableDeployment | Deployment | Deployment |
GetDeployment | MixedOperation | Deployment | Deployment |
DeleteDeployment | N/A | Boolean | Boolean |
Name | config{} |
onNext{} |
response() |
---|---|---|---|
ListNamespaces | NonNamespaceOperation | Namespace | NamespaceList |
CreateNamespace | MetadataNestedImpl | Namespace | Namespace |
GetNamespace | N/A | Namespace | Namespace |
DeleteNamespace | N/A | Boolean | Boolean |
Name | config{} |
onNext{} |
response() |
---|---|---|---|
ListPods | MixedOperation | Pod | PodList |
CreatePod | DoneablePod | Pod | Pod |
GetPod | N/A | Pod | Pod |
DeletePod | N/A | Boolean | Boolean |
Name | config{} |
onNext{} |
response() |
---|---|---|---|
ListServices | MixedOperation | Service | ServiceList |
CreateService | DoneableService | Service | Service |
GetService | N/A | Service | Service |
DeleteService | N/A | Boolean | Boolean |
Name | config{} |
onNext{} |
response() |
---|---|---|---|
Configuration | N/A | Configuration | Configuration |
This plugin provides various means of configuring, working with, and accessing properties and values of a given Task
. Through the use of config{}
, response()
, and reactive-streams
, which are each further documented below, the user is given full access to configure their Task
however the choose, have full access to the response or output of a given Task
, and be able to work more closely with the life-cycle of a given task.
All tasks, as well as the kubernetes
extension point, implement the ConfigAware trait. This in turn exposes the config{}
closure allowing developers to further configure said objects. The respective config{}
closure maps to the backing/documented object. A typical use-case would be to configure a task like so:
task myCustomNameSpace(type: CreateNamespace) {
config {
withName("hello-world") // applying name via `config{}` construct instead of property
}
}
The config{}
closure has its delegate (as well as the first parameter) set to the object you're allowed to configure within a given context. In the example above, and documented in the table below, the CreateNamespace
task allows you to configure the MetadataNestedImpl
object which really can just be thought of as a super-class to the internal Namespace
instance. In java, and using the same kubernetes-client
, this would look something like:
NonNamespaceOperation<Namespace, NamespaceList, DoneableNamespace, Resource<Namespace, DoneableNamespace>> namespaces = client.namespaces();
Resource<Namespace, DoneableNamespace> withName = namespaces.withName("hello-world");
The config{}
closure is an attempt at trying to provide a common means of configuring Objects in a very gradle like fashion. This is considered an ADVANCED feature so please only use if the OOTB supplied properties are not enough.
All tasks, as well as the kubernetes
extension point, implement the RetryAware trait. This allows the developer to define a common means, via the retry{}
closure, to configure how tasks should be re-run. Developers can either define the retry{}
closure on the extension point or on the task itself for more granular configurations. Some typical use-cases are as follows:
When defined on the extension:
kubernetes {
retry {
withDelay(10, TimeUnit.SECONDS)
withMaxRetries(3)
}
}
When defined on a task:
task getNamespace(type: GetNamespace) {
namespace = "my-eventually-existing-namespace"
retry {
withDelay(10, TimeUnit.SECONDS)
withMaxRetries(3)
}
}
Task definitions of the retry{}
closure take precedence over those defined on the extension point.
We use the failsafe library behind the scenes to execute code internally. Thus when you define/code a retry{}
closure you're actually configuring a newly created instance of RetryPolicy.
It should be noted that we are not actually re-running the actual Task
through some gradle magic done behind the scenes. Instead we are just re-running the internal "execution block" that each task implements.
All tasks implement the ResponseAware trait. As such the end-user, and ONLY upon completion of a given task, will be able to query for a given tasks response()
object. Furthermore the Object returned is different for each task and is documented in the table below.
As each task does some work in the backend it's sometimes helpful, or even desired, to get the returned object for further downstream inspection. Suppose you wanted a programmatic way of getting the name of the namespace you just created. You could do something like:
task myCustomNameSpace(type: CreateNamespace) {
config {
withName("hello-world") // applying name via `config{}` construct instead of property
}
}
task downstreamTask(dependsOn: myCustomNameSpace) {
doLast {
def foundName = myCustomNameSpace.response().getMetadata().getName()
// now do something with the `foundName` String
}
}
Much like the config{}
closure the response()
method is an attempt at providing a standard way across all tasks of accessing the returned Object from the internal kubernetes-client
invocation.
reactive-streams support is an optional feature you can take advantage of and works for all tasks. We try to align with best practices but given that we are executing within a gradle context we break the expected API from time to time to keep the look and feel of our plugin. Each task generally behaves the same but if one doesn't please visit have a look at the task definition itself for any documentaiton or nuance surrounding its use.
Documentation on how we implement this feature can be found in our HERE. Examples to help you get started can be found HERE.
The onError
closure is passed the exception that is thrown for YOU to handle. If you silently ignore we will not throw/re-throw the exception behind the scenes. Suppose you want to automate the creation of a Namespace
but you don't want to fail if the namespace already exists. In this scenario you could do something like the below:
import com.bmuschko.gradle.kubernetes.plugin.tasks.namespaces.CreateNamespace
task createNamespace(type: CreateNamespace) {
namespace = "namespace-that-possibly-exists"
onError { exception ->
if (exception.message.contains('namespace already exists')) { // not an actual message just an example
// do nothing
} else {
throw execption
}
}
}
The onNext
closure is passed the next iterative response upon execution or if the response contains a list then the next item in that list. For all other tasks we simply hand back the object that is given to us by the execution.
Iterative tasks are things like ListNamespaces
or ListPods
. These tasks have output which can be iterated over or return a list (e.g. Collection
or Object[]
) of some sort.
Suppose we want to list out, or simply work with, each available namespace. We might do something like:
import com.bmuschko.gradle.kubernetes.plugin.tasks.namespaces.ListNamespaces
task listNamespaces(type: ListNamespaces) {
onNext { namespace ->
logger.quiet "Found namespace: ${namespace.name()}"
}
}
If 4
namespaces were present then the above onNext
closure would execute for each found.
The onComplete
closure is not passed anything upon execution. It works in the same fashion that doLast
does but is instead part of this task and thus executes before doLast
kicks. This closure executes ONLY upon success. The below example demonstrates how this works.
import com.bmuschko.gradle.kubernetes.plugin.tasks.namespaces.GetNamespace
task getNamespace(type: GetNamespace) {
namespace = "my-namespace"
onComplete {
logger.quiet 'Executes first'
}
doLast {
logger.quiet 'Executes second'
}
}
The functionalTests provide many examples that you can use for inspiration within your own code. If there are any questions about how to use a given feature feel free to open an issue and just ask.