OpenShift API examples

8 minutes read

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.

secret.json
 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.

POST /api/v1/namespaces/<project>/secrets
 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.

GET /api/v1/namespaces/<project>/secrets/foobar
 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.

PUT /oapi/v1/namespaces/<project>/deploymentconfigs/<deploymentconfig>
  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

PUT /api/v1/namespaces/<project>/serviceaccounts/<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.

POST /api/v1/namespaces/<project>/secrets
 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.

PUT /api/v1/namespaces/<project>/serviceaccounts/<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":_}}.

  1. Get the current value of latestVersion.

    GET /oapi/v1/namespaces/<project>/deploymentconfigs/<deploymentconfig>
    1
    2
    3
    4
    5
    6
    
    {
    ...
      "status": {
        "latestVersion": 1
      }
    }
    
  2. 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

POST /oapi/v1/namespaces/<project>/routes
 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

POST /api/v1/namespaces/<project>/services
 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.

POST /api/v1/namespaces/<project>/services
 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.

POST /oapi/v1/namespaces/<project>/deploymentconfigs
 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
  }
}