OpenShift API requests
OpenShift reuses the API exposed by Kubernetes at /api/v1
and has
OpenShift specific endpoints exposed at /oapi/v1
.
Both APIs will require a valid token, which can be obtained from a
user or service account with proper permissions on the desired
project.
User account
$ oc login -u user1 https://<ose-master>:8443
$ oc whoami -t > token.txt
$ cat token.txt
YrxrxRxiZOGRXCyBhWrWvqly7NsOY0T0du2jpRHqzA
Service account
$ oc create serviceaccount api
$ oc policy add-role-to-user edit system:serviceaccount:project1:api
$ oc describe serviceaccount api|grep Tokens
Tokens: api-token-iy0x2
$ oc describe secret api-token-iy0x2 | grep '^token' | awk '{print
$2}' > token.txt
$ cat token.txt
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJwcm9qZWN0MSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhcGktdG9rZW4taXkweDIiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoiYXBpIiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQudWlkIjoiNzI2NTljN2EtZjg0NS0xMWU2LWFkNTEtNTI1NDAwZWM3MWM1Iiwic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OnByb2plY3QxOmFwaSJ9.PZltqgHW8_n6qjHn07yJwHGHFAbXH767qM_--5m44U6SLWZhgO2fIY3A3nwj_MnvndsR_FczPYS6fUBxDjKWg6JEWeDsNAlMw7m_vaOxXpNZsRL-8AX6adwsLx8xyzSYWVUj7qB8Bq_zaQAbXQDLAjoCFTzrXSf15TPYe6JTiCEEB4DSrNWDcXj1XF8YErai1YEsq6-Azp7x6AyArVFRf61xoja9VvFZpzn4QCUl_OTcjspp_iARVdOC1dev3xDD0tSWgcuICb27i73jI9J3scEdfBlMLxm_EQBuftcR2TiwlF_LiwjgDDnD_m6UAMhFfILt1J72-x30bkvzxGhD_A
Without the oc client
$ curl -u myuser:mypassword -ksIl 'https://127.0.0.1:8443/oauth/authorize?client_id=openshift-challenging-client&response_type=token' | grep ^Location
Location: https://10.1.2.2:8443/oauth/token/implicit#*access_token=CdmUsE5xG-_bwMdYlaoyVF6Ks25RkMRt4pPDXSl2C8U*&expires_in=86400&token_type=Bearer
The token can then be used as follows for authenticating to the API.
curl -H "Authorization: Bearer $(cat token.txt)" https://.....
Secrets
Create secrets
$ echo -n 's3cr3t!' | base64 > secret.txt
$ cat secret.txt
czNjcjN0IQ==
Define your secret object.
1
2
3
4
5
6
7
8
9
10
11
12
{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "foobar",
"namespace": "project1"
},
"data": {
"password": "czNjcjN0IQ=="
},
"type": "kubernetes.io/basic-auth"
}
Create the secret.
1
2
3
4
5
6
7
8
9
10
11
{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "foobar",
},
"data": {
"password": "czNjcjN0IQ=="
},
"type": "kubernetes.io/basic-auth"
}
Retreive the created secret.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "foobar",
"namespace": "<project>",
"selfLink": "/api/v1/namespaces/<project>/secrets/foobar",
"uid": "faf4cffe-f48d-11e6-bade-525400ec71c5",
"resourceVersion": "2708",
"creationTimestamp": "2017-02-16T21:22:11Z"
},
"data": {
"password": "czNjcjN0IQ=="
},
"type": "kubernetes.io/basic-auth"
}
Mount secret to a pod
This can be done by adding/updating the deploymentconfig with the
desired volume and secret. The example below will mount app-secrets
under /app-secrets
inside the
container.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
{
"kind": "DeploymentConfig",
"apiVersion": "v1",
"metadata": {
"name": "myapp",
"resourceVersion":"117464", (1)
"labels": {
"app": "myapp"
}
},
"spec": {
"strategy": {
"type": "Rolling",
"rollingParams": {
"updatePeriodSeconds": 1,
"intervalSeconds": 1,
"timeoutSeconds": 600,
"maxUnavailable": "25%",
"maxSurge": "25%"
},
"resources": {}
},
"triggers": [
{
"type": "ConfigChange"
},
{
"type": "ImageChange",
"imageChangeParams": {
"automatic": true,
"containerNames": ["myapp"],
"from": {
"kind": "ImageStreamTag",
"namespace": "api",
"name": "myapp:latest"
}
}
}
],
"replicas": 1,
"test": false,
"selector": {
"app": "myapp",
"deploymentconfig": "myapp"
},
"template": {
"metadata": {
"creationTimestamp": null,
"labels": {
"app": "myapp",
"deploymentconfig": "myapp"
},
"annotations": {
"openshift.io/container.myapp.image.entrypoint": "[\"httpd-foreground\"]"
}
},
"spec": {
"volumes": [
{
"name": "volume-app-secrets",
"secret": {
"secretName": "app-secrets"
}
}
],
"containers": [
{
"name": "myapp",
"image": "172.30.147.195:5000/api/myapp@sha256:248cf318188bda2cb65b345fd85e31662078e73a92e45a4242b989f6064fcbbd",
"ports": [
{
"containerPort": 80,
"protocol": "TCP"
}
],
"resources": {},
"volumeMounts": [
{
"name": "volume-app-secrets",
"mountPath": "/app-secrets"
}
],
"terminationMessagePath": "/dev/termination-log",
"imagePullPolicy": "IfNotPresent"
}
],
"restartPolicy": "Always",
"terminationGracePeriodSeconds": 30,
"dnsPolicy": "ClusterFirst",
"securityContext": {}
}
}
},
"status": {
"latestVersion": 2, (2)
"details": {
"causes": [
{
"type": "ConfigChange"
}
]
}
}
}
1 | Ensure the resourceVersion matches what’s currently deployed. |
2 | Increase latestVersion , typically by one. |
Add a secret to a service account
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"kind": "ServiceAccount",
"apiVersion": "v1",
"metadata": {
"name": "default",
"namespace":"api",
"resourceVersion":"117340", (1)
},
"secrets": [
{
"name": "default-token-912kc"
},
{
"name": "default-dockercfg-0k6os"
},
{
"name": "app-secrets" (2)
}
],
"imagePullSecrets": [
{
"name":
"default-dockercfg-0k6os"
}
]
}
1 | Ensure the resourceVersion matches what’s currently deployed. |
2 | The secret to be added |
Create a docker pull secret
This can be done by creating a new secret object.
1
2
3
4
5
6
7
8
9
10
11
12
{
"kind": "Secret",
"apiVersion": "v1",
"metadata": {
"name": "172.30.147.195"
},
"data": {
".dockercfg":
"eyIxNzIuMzAuMTQ3LjE5NTo1MDAwIjp7InVzZXJuYW1lIjoiRE9DS0VSX1VTRVIiLCJwYXNzd29yZCI6IkRPQ0tFUl9QQVNTV09SRCIsImVtYWlsIjoiRE9DS0VSX0VNQUlMIiwiYXV0aCI6IlJFOURTMFZTWDFWVFJWSTZSRTlEUzBWU1gxQkJVMU5YVDFKRSJ9fQ==" (1)
},
"type": "kubernetes.io/dockercfg"
}
1 | The secret string has to be base64 encoded |
Let’s also take a closer look at what the .dockercfg
object actually
looks like.
echo -n
'eyIxNzIuMzAuMTQ3LjE5NTo1MDAwIjp7InVzZXJuYW1lIjoiRE9DS0VSX1VTRVIiLCJwYXNzd29yZCI6IkRPQ0tFUl9QQVNTV09SRCIsImVtYWlsIjoiRE9DS0VSX0VNQUlMIiwiYXV0aCI6IlJFOURTMFZTWDFWVFJWSTZSRTlEUzBWU1gxQkJVMU5YVDFKRSJ9fQ=='
|base64 -d | jq .
{
"172.30.147.195:5000": {
"username": "DOCKER_USER",
"password": "DOCKER_PASSWORD",
"email": "DOCKER_EMAIL",
"auth": "RE9DS0VSX1VTRVI6RE9DS0VSX1BBU1NXT1JE" (1)
}
}
1 | The auth field is a base64 encoded string of your {username}:{password} |
Add docker pull secret to a service account
This is very similar to adding a generic secret to a service account.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"kind": "ServiceAccount",
"apiVersion": "v1",
"metadata": {
"name": "default",
"namespace":"api",
"resourceVersion": "145123" (1)
},
"secrets": [
{
"name": "default-token-912kc"
},
{
"name": "default-dockercfg-0k6os"
},
{
"name": "app-secrets"
}
],
"imagePullSecrets": [
{
"name": "default-dockercfg-0k6os"
},
{
"name": "172.30.147.195" (2)
}
]
}
1 | Ensure the resourceVersion matches what’s currently deployed. |
2 | The secret to be added |
Patching an object
The API(s) support the HTTP PATCH
method which can be very
convenient when updating larger objects. It’s important to notice that
the Content-Type
header should be
application/strategic-merge-patch+json
for this to work.
Below is an example of how to re-deploy an application by incrementing
{"status":{"latestVersion":_}}
.
-
Get the current value of
latestVersion
.GET /oapi/v1/namespaces/<project>/deploymentconfigs/<deploymentconfig>1 2 3 4 5 6
{ ... "status": { "latestVersion": 1 } }
-
Send the patch request
PATCH /oapi/v1/namespaces/<project>/deploymentconfigs/<deploymentconfig>1 2 3 4 5
{ "status": { "latestVersion": 2 (1) } }
1 Increment latestVersion
by one.
Create a new Route object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"kind": "Route",
"apiVersion": "v1",
"metadata": {
"name": "myapp",
"labels": {
"app": "myapp"
}
},
"spec": {
"host":"",
"to": {
"name": "myapp" (1)
},
"port": {
"targetPort": "80-tcp" (2)
}
},
"status": {
"ingress": null
}
}
1 | The service the route should forward traffic to. |
2 | The target port on pods selected by the service this route points to. |
Create a Service object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"kind": "Service",
"apiVersion": "v1",
"metadata": {
"name": "myapp",
"labels": {
"app": "myapp"
}
},
"spec": {
"ports": [
{
"protocol": "TCP",
"port": 20000 (1)
}
],
"selector": { (2)
"app": "myapp",
"deploymentconfig": "myapp"
}
},
"status": {
"loadBalancer": {}
}
}
1 | The port that will be exposed by this service. |
2 | Route service traffic to pods with label keys and values matching this selector. |
API Status object
Successful requests generally returns the object with a few added fields such at creation timestamps as well as the status of the object.
A failed request will return a Status
object with the error message.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {},
"status": "Failure",
"message": "services \"myapp\" already exists",
"reason": "AlreadyExists",
"details": {
"name": "myapp",
"kind": "services"
},
"code": 409
}
Create a new deployment
This example will deploy the
hello-openshift
container from docker hub.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
{
"apiVersion": "v1",
"kind": "DeploymentConfig",
"metadata": {
"annotations": {},
"labels": {
"app": "hello-openshift"
},
"name": "hello-openshift"
},
"spec": {
"replicas": 1,
"selector": {
"app": "hello-openshift",
"deploymentconfig": "hello-openshift"
},
"strategy": {
"resources": {},
"rollingParams": {
"intervalSeconds": 1,
"maxSurge": "25%",
"maxUnavailable": "25%",
"timeoutSeconds": 600,
"updatePeriodSeconds": 1
},
"type": "Rolling"
},
"template": {
"metadata": {
"annotations": {
"openshift.io/container.hello-openshift.image.entrypoint":
"[\"/hello-openshift\"]"
},
"labels": {
"app": "hello-openshift",
"deploymentconfig": "hello-openshift"
}
},
"spec": {
"containers": [
{
"image": "openshift/hello-openshift:latest",
"imagePullPolicy": "IfNotPresent",
"name": "hello-openshift",
"ports": [
{
"containerPort": 8080,
"protocol": "TCP"
},
{
"containerPort": 8888,
"protocol": "TCP"
}
],
"resources": {},
"terminationMessagePath": "/dev/termination-log"
}
],
"dnsPolicy": "ClusterFirst",
"restartPolicy": "Always",
"securityContext": {},
"terminationGracePeriodSeconds": 30
}
},
"test": false,
"triggers": [
{
"type": "ConfigChange"
},
{
"type": "ImageChange",
"imageChangeParams": {
"automatic": true,
"containerNames": [
"hello-openshift"
],
"from": {
"kind": "ImageStreamTag",
"name": "hello-openshift:latest"
}
}
}
]
},
"status": {
"latestVersion": 1
}
}