OKD Node.js Client
This Node.JS RESTful client API for Kubernetes/OpenShift. This library helps you use JavaScript to write simple programs to automate things like deploy images, listen for cluster events, build/create containers (OpenShift only at the moment), watch push/pull images etc. Or more sophisticated things like send me an email/slack message if a particular pod crash.
Index
Installing
npm i okd-api
const { login, okd } = require('okd-api')
Login into OpenShift
This API exposes two objects login
which handles the login against a OpenShift cluster.
let config = {
cluster:'https://okd.address.com/',
user: 'user',
password: '***',
strictSSL: true || false
}
login(config)
.then(okd => )
.catch(err => )
The login function receive an configuration following object:
The login functions returns an okd
object after it finish the authentication with the server, the okd
object is the one in charge of to talk against the API server.
With Token
If you already have a token (because you previously acquired one through authentication) you can provide the cluster URL and the token.
const cluster = `https://minishfit.vm:8443/`
const token = 'v0tDED5vjN7Vv...'
let imagestream = okd(cluster, token).namespace('dev-665').is
imagestream.all().then()
.catch()
Namespace
Namespaces is a way to partition resources across your Kubernetes/OpenShift cluster. This mean that to access a particular resource such as BuildConfig you need first need to specify the namespace.
okd.namespace('dev-665')
okd.svc.all()
If you need to operate across multiple namespaces you just need to instantiate multiple objects.
function useNS(ns) {
const cluster = `https://my-cluster.com/`
const token = 'v0tDED5vjN7Vv...'
return okd(cluster, token).namespace(ns)
}
myScripts.forEach(script => useNS('A').from_template('app-1',script).post())
myScripts.forEach(script => useNS('B').from_template('app-1',script).post())
myScripts.forEach(script => useNS('C').from_template('app-1',script).post())
Retrieve Token
Once the client login with the server you receive an OAuth token to get this token and other information by calling the config
method.
okd.config( opts => {
console.log(opts)
})
Components
This are the objects supported at the moment:
To access those elements like this:
okd.is
okd.bc
okd.build
okd.project
okd.dc
okd.route
okd.svc
okd.pod
okd.deploy
okd.rs
okd.namespace
CRUD
Each of this objects support a common set of functionalities which are related to the basic HTTP REST verbs. Let's talk in more detail about those functions.
Find All
Returns a promise which resolve in the future with a list of all the objects of a particular type.
okd.namespace('dev-665')
.is
.all()
okd.namespace('dev-1')
.svc
.all()
By Name
You can also find resources by name:
okd.namespace('dev-665')
.is
.by_name('nodejs-image')
Returns Imagestream called nodejs-image
.
Remove
To remove a resource from the cluster:
okd.namespace('dev-665')
.is
.remove('nodejs-image')
It returns a promise with the response from the server.
Creating
To create Kubernetes/OpenShift objects you can use the following:
let deploy = okd.namespace('dev-665').deploy
deploy.post('name')
Usually you want to describe the configuration of your components and for that reason there is a template system.
Template
Objects in Kubernetes are defined using a templates like this one:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: <%=name%>
labels:
app: <%=name%>
spec:
replicas: 1
selector:
matchLabels:
app: <%=name%>
template:
metadata:
labels:
app: <%=name%>
spec:
containers:
- name: <%=name%>
image: <%=image%>
command: ['sh', '-c', 'echo Hello Kubernetes! && sleep 3600']
ports:
- containerPort: 8080
This is what a Deployment looks like, this API support template parameters that helps you create reusable templates, in this example we can replace the <%=name%>
and <%=image%>
placeholders with actual information like this:
let deploy = okd.namespace('dev-665')
.deploy
deploy.load({name: 'my-deployment', image:'nginx'}, 'deploy.yml')
.post()
This code push to the server a Deployment object with the following shape:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: my-deployment
labels:
app: my-deployment
spec:
replicas: 1
...
containers:
image: nginx
...
Editing template at Runtime
To modify the template at run-time you can do the following:
let deploy = okd.namespace('dev-665').deploy
let object = deploy.load({name: 'my-deployment', image:'nginx'}, 'deploy.yml')._tmpl
object.metadata.name = 'deploy-y'
deploy.post()
Faster Template
Template operations above can be done faster by using this shortcut:
okd.namespace('dev-003')
okd.from_template(this.appName,'./tmpl/imagestream.yml').post()
from_template will auto-detect the type of template you try to use and if its supported you can perform the actions for example:
let deploy = okd.from_template(this.appName,'./tmpl/deployment.yml')
If deployment.yml
is a valid Deployment template it will return an okd.deploy
object, equivalent to something like this:
let deploy = okd.deploy
The difference is that the information about the deployment is encapsulated inside the object, allowing you act over the object.
let objs = ['./deploy.yml','pod.yml', 'route.yml' ]
.map(tmpl => okd.from_template(tmpl))
objs.forEach(obj => obj.create())
objs.forEach(obj => obj.remove())
Update
To update a Kubernetes component such as Deployment you can use the put
or update
method, this method fetch a copy of the actual resource from the server and apply a merge, for example:
Let's assume an imaginary object A
is in the cloud:
A {
a:1,
b:2,
c:3,
d: { e: 5 }
}
To update the e
property to 1 we can do:
okd.Atype.put( { b: {e: 1 } })
.then(ok => )
After we execute this command we should get this:
A {
a:1,
b:2,
c:3,
d: { e: 1 }
}
In the following example we are going to update a Deployment controller test
to 3 replicas.
login(store.configuration)
.then(okd => {
okd.namespace('hello')
okd.config((conf) => store.save(conf))
return okd.deploy.put('test', {spec: { replicas: 3 }})
})
.then( ok => console.log('update->', ok))
.catch(err => console.log('failing: ', err))
Patch
Another way to update Kubernetes object is to use the patch
method, this method requires the json-PATCH protocol, for example:
let update = {
op:'replace',
path:'/spec/template/spec/containers/0/image',
value: 'awesome:test-23'
}
let deploy = okd.namespace('dev-001').deploy
deploy.patch('awesome-app', update)
This will update the deployment object and automatically this change will trigger a re-deployment.
Watch
Kubernetes/OpenShift uses an event system to keep track of changes in the cluster, we can listen to this changes individually using the watch
function.
okd.okd_object.watch(name, event => {})
The event have the form of:
{
type: "MODIFIED",
object: {
kind: "Service",
apiVersion: "v1",
}
}
Type define the action and object is basically a Kubernetes/OKD object definition.
Usage Example
okd.namespace('hello')
okd.is.watch('micro-1', (event)=> {
if (event.type === 'MODIFIED') {
}
})
okd.pod.watch('nginx-prod', (event)=> {
if (event.type === 'DELETED') {
}
})
Watching All Objects
Or we can watch them all using the watch_all
function, and listen any events in the namespace
for a particular object type.
okd.resource.watch_all( (events) => { } )
This function receives array of events as parameters. To watch the pods running we can do this:
const login = require('okd-api').login
login(store.configuration)
.then(okd => okd.namespace('testing')
.pod
.watch_all(watch_test))
.catch(err => console.log('Authentication error: ', err))
We call a watch_all
function a pass a function called watch_test
this function should have the following signature:
function watch_test(events) {
let type = events[0].type
let phase = events[0].object.status.phase
let pod_name = events[0].object.metadata.name
if(!( 'openshift.io/build.name' in annotations) ) {
console.log(`event type ${events[0].type} -> ${pod_name}`)
console.log(`phase => `, phase)
}
}
Contrary to the cases above, here should use a callback because we are dealing with a stateful connection which will remain as long as the timeout limit establish by the cluster administrator. The event type is similar to the one we used above.
![](https://github.com/cesarvr/hugo-blog/blob/master/static/static/gifs/global-events.gif?raw=true)
Concrete Implementations
Some Kubernetes/OKD objects have unique functionalities, in the case of pod for example aside from common functionalities they also implement other functions like logs, exec, etc.
Build Configuration
Trigger A Binary Build
For now the only way to trigger a build in using this API is by uploading a binary.
For example:
function compressWorkSpace(dir, name){
let tmp_file = name || './okd.tar.gz'
let ret = spawn('tar', ['-Czf', tmp_file, '-C', dir, dir])
return tmp_file
}
let file = compressWorkSpace('build.tar.gz', '~/my-nodejs-project')
okd.bc.binary(file, 'micro-1')
.then(ok => console.log('The build has started...'))
.catch(noErrors)
Pods
Pods are the building blocks for Kubernetes applications, they also expose some functionalities that can be useful:
Logs
There is two options to watch pod logs first is getting the whole bulk, by using the pod.logs
method:
okd.pod.by_name('my-pod')
.then(pod => pod.metadata.name)
.then(name => okd.pod.logs(name))
.then(logs => console.log(logs))
This will return the logs for the pod my-pod
npm info using npm@6.4.1
npm info using node@v10.12.0
npm info lifecycle my-app@1.0.0~prestart: my-app@1.0.0
npm info lifecycle my-app@1.0.0~start: my-app@1.0.0
> my-app@1.0.0 start /opt/app-root/src
> node app.js
This method give you a snapshot of the current state but you will miss further updates, to keep streaming logs in real-time you can use the pod.stream_logs
method:
okd.pod.stream_logs(podName, line => {
console.log(line)
})
This method keeps track of the latest logs update in the pod.
If you are running multiple containers in a single pod then you can target that container using function container
like this:
okd.namespace('dev-0')
.pod
.container('ftp-server')
.logs('static-files-pod')
.then(logs => {
})
Writing A Bot
Let's build a bot that fetch the logs of any container that is being deploying in our cluster, this can be interesting to follow the stages of an applications from building, testing and execution from one place.
Login
const { login } = require('okd-api')
login(store.configuration)
.then(okd => okd.namespace('testing')
.pod
.watch_all( pods =>
)
.catch(err => console.log('Authentication error: ', err))
We have done the login and we setup the watch for the namespace testing
next we need to create a function that watch and capture the transition of pods from Pending
to Running
.
function watch_bot(okd, name) {
let pending = {}
return function(events) {
let annotations = events[0].object.metadata.annotations
let labels = events[0].object.metadata.labels
let phase = events[0].object.status.phase
let pod_name = events[0].object.metadata.name
if(phase === 'Pending') {
console.log(`event type ${events[0].type} -> ${pod_name}`)
console.log(`phase => `, events[0].object.status.phase)
pending[pod_name] = true
}
if(phase === 'Running' && pending[pod_name]) {
console.log('\033[2J')
console.log('pod: ', pod_name)
console.log('================================')
okd.pod.stream_logs(pod_name, (logs)=> {
process.stdout.write(logs)
})
pending[pod_name] = false
}
}
}
This function does just that it just listen for pods doing the transitions and then we finally use the pod.stream_logs
to retrieve the logs from the targeted pod. If we run this program we are going to get something like this:
![](https://github.com/cesarvr/hugo-blog/blob/master/static/static/gifs/logs.gif?raw=true)