Skip to content

Commit a8811ae

Browse files
author
Jim Minter
committed
Go client documentation
1 parent 4b17aa6 commit a8811ae

8 files changed

+1206
-0
lines changed

_topic_map.yml

+18
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,24 @@ Topics:
10271027
File: revhistory_apb_devel
10281028
Distros: openshift-enterprise
10291029
---
1030+
Name: Go Client Library Reference
1031+
Dir: go_client
1032+
Topics:
1033+
- Name: Getting Started
1034+
File: getting_started
1035+
- Name: Connecting to the Cluster
1036+
File: connecting_to_the_cluster
1037+
- Name: Tracing API Requests and Responses
1038+
File: tracing_api_requests_and_responses
1039+
- Name: Standard API Operations
1040+
File: standard_api_operations
1041+
- Name: Serializing and Deserializing
1042+
File: serializing_and_deserializing
1043+
- Name: Instantiating Templates
1044+
File: instantiating_templates
1045+
- Name: Executing Remote Processes
1046+
File: executing_remote_processes
1047+
---
10301048
Name: REST API Reference
10311049
Dir: rest_api
10321050
Topics:
+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
[[go-client-connecting-to-the-cluster]]
2+
= Connecting to the cluster
3+
{product-author}
4+
{product-version}
5+
:data-uri:
6+
:icons:
7+
:experimental:
8+
:toc: macro
9+
:toc-title:
10+
11+
toc::[]
12+
13+
== Overview
14+
15+
Common ways to connect to {product-title} include "from the outside" (via a
16+
pre-existing kubeconfig file), and "from the inside" (running within a Pod,
17+
using the Pod's ServiceAccount principal).
18+
19+
== Connecting via a pre-existing kubeconfig file
20+
21+
An example showing how to connect to {product-title} via a pre-existing
22+
kubeconfig file is included in
23+
xref:getting_started.adoc#go-client-getting-started[Getting Started].
24+
25+
In particular, the example shows:
26+
27+
1. Instantiating a loader for the kubeconfig file:
28+
+
29+
[source, go]
30+
----
31+
kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
32+
clientcmd.NewDefaultClientConfigLoadingRules(),
33+
&clientcmd.ConfigOverrides{},
34+
)
35+
----
36+
37+
1. Determining the namespace referenced by the current context in the kubeconfig
38+
file:
39+
+
40+
[source, go]
41+
----
42+
namespace, _, err := kubeconfig.Namespace()
43+
----
44+
45+
1. Getting a rest.Config from the kubeconfig file. This is passed into all the
46+
client objects created:
47+
+
48+
[source, go]
49+
----
50+
restconfig, err := kubeconfig.ClientConfig()
51+
----
52+
53+
1. Creating clients from the rest.Config:
54+
+
55+
[source, go]
56+
----
57+
coreclient, err := corev1client.NewForConfig(restconfig)
58+
buildclient, err := buildv1client.NewForConfig(restconfig)
59+
----
60+
61+
== Connecting from within a pod running in the cluster
62+
63+
The following example connects to {product-title} from within a Pod, using the
64+
Pod's ServiceAccount principal.
65+
66+
.$GOPATH/src/gettingstarted/main.go
67+
[source, go]
68+
----
69+
package main
70+
71+
import (
72+
"fmt"
73+
"net/http"
74+
"os"
75+
76+
buildv1client "github.com/openshift/client-go/build/clientset/versioned/typed/build/v1"
77+
78+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
79+
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
80+
"k8s.io/client-go/rest"
81+
)
82+
83+
func main() {
84+
// Build a rest.Config from configuration injected into the Pod by
85+
// Kubernetes. Clients will use the Pod's ServiceAccount principal.
86+
restconfig, err := rest.InClusterConfig()
87+
if err != nil {
88+
panic(err)
89+
}
90+
91+
// If you need to know the Pod's Namespace, adjust the Pod's spec to pass
92+
// the information into an environment variable in advance via the downward
93+
// API.
94+
namespace := os.Getenv("NAMESPACE")
95+
if namespace == "" {
96+
panic("NAMESPACE was not set")
97+
}
98+
99+
// Create a Kubernetes core/v1 client.
100+
coreclient, err := corev1client.NewForConfig(restconfig)
101+
if err != nil {
102+
panic(err)
103+
}
104+
105+
// Create an OpenShift build/v1 client.
106+
buildclient, err := buildv1client.NewForConfig(restconfig)
107+
if err != nil {
108+
panic(err)
109+
}
110+
111+
mux := http.NewServeMux()
112+
mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
113+
rw.Header().Set("Cache-Control", "no-store, must-revalidate")
114+
rw.Header().Set("Content-Type", "text/plain")
115+
116+
// List all Pods in our current Namespace.
117+
pods, err := coreclient.Pods(namespace).List(metav1.ListOptions{})
118+
if err != nil {
119+
panic(err)
120+
}
121+
122+
fmt.Fprintf(rw, "Pods in namespace %s:\n", namespace)
123+
for _, pod := range pods.Items {
124+
fmt.Fprintf(rw, " %s\n", pod.Name)
125+
}
126+
127+
// List all Builds in our current Namespace.
128+
builds, err := buildclient.Builds(namespace).List(metav1.ListOptions{})
129+
if err != nil {
130+
panic(err)
131+
}
132+
133+
fmt.Fprintf(rw, "Builds in namespace %s:\n", namespace)
134+
for _, build := range builds.Items {
135+
fmt.Fprintf(rw, " %s\n", build.Name)
136+
}
137+
})
138+
139+
// Run an HTTP server on port 8080 which will serve the pod and build list.
140+
err = http.ListenAndServe(":8080", mux)
141+
if err != nil {
142+
panic(err)
143+
}
144+
}
145+
----
146+
147+
Note: to try out the above example, you will need to ensure:
148+
149+
1. the Pod's ServiceAccount (called "default" by default) has permissions to
150+
list Pods and Builds. One way to achieve this is by running `oc policy
151+
add-role-to-user view -z default`.
152+
153+
1. the downward API is used to pass the Pod's Namespace into an environment
154+
variable, so that it can be picked up by the application. The following Pod
155+
spec achieves this:
156+
+
157+
[source, yaml]
158+
----
159+
kind: Pod
160+
apiVersion: v1
161+
metadata:
162+
name: getting-started
163+
spec:
164+
containers:
165+
- name: c
166+
image: ...
167+
env:
168+
- name: NAMESPACE
169+
valueFrom:
170+
fieldRef:
171+
fieldPath: metadata.namespace
172+
----
+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
[[go-client-executing-remote-processes]]
2+
= Executing Remote Processes
3+
{product-author}
4+
{product-version}
5+
:data-uri:
6+
:icons:
7+
:experimental:
8+
:toc: macro
9+
:toc-title:
10+
11+
With the `pods/exec` permission, it is possible to remotely execute additional
12+
processes in the context of a Container in a Pod. The following example
13+
demonstrates how to do this using the {product-title} client library.
14+
15+
.$GOPATH/src/gettingstarted/main.go
16+
[source, go]
17+
----
18+
package main
19+
20+
import (
21+
"fmt"
22+
"os"
23+
24+
"golang.org/x/crypto/ssh/terminal"
25+
26+
corev1 "k8s.io/api/core/v1"
27+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/watch"
29+
"k8s.io/client-go/kubernetes/scheme"
30+
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
31+
"k8s.io/client-go/tools/clientcmd"
32+
"k8s.io/client-go/tools/remotecommand"
33+
)
34+
35+
func main() {
36+
// Instantiate loader for kubeconfig file.
37+
kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
38+
clientcmd.NewDefaultClientConfigLoadingRules(),
39+
&clientcmd.ConfigOverrides{},
40+
)
41+
42+
// Determine the Namespace referenced by the current context in the
43+
// kubeconfig file.
44+
namespace, _, err := kubeconfig.Namespace()
45+
if err != nil {
46+
panic(err)
47+
}
48+
49+
// Get a rest.Config from the kubeconfig file. This will be passed into all
50+
// the client objects we create.
51+
restconfig, err := kubeconfig.ClientConfig()
52+
if err != nil {
53+
panic(err)
54+
}
55+
56+
// Create a Kubernetes core/v1 client.
57+
coreclient, err := corev1client.NewForConfig(restconfig)
58+
if err != nil {
59+
panic(err)
60+
}
61+
62+
// Create a busybox Pod. By running `cat`, the Pod will sit and do nothing.
63+
var zero int64
64+
pod, err := coreclient.Pods(namespace).Create(&corev1.Pod{
65+
ObjectMeta: metav1.ObjectMeta{
66+
Name: "busybox",
67+
},
68+
Spec: corev1.PodSpec{
69+
Containers: []corev1.Container{
70+
{
71+
Name: "busybox",
72+
Image: "busybox",
73+
Command: []string{"cat"},
74+
Stdin: true,
75+
},
76+
},
77+
TerminationGracePeriodSeconds: &zero,
78+
},
79+
})
80+
if err != nil {
81+
panic(err)
82+
}
83+
84+
// Delete the Pod before we exit.
85+
defer coreclient.Pods(namespace).Delete(pod.Name, &metav1.DeleteOptions{})
86+
87+
// Wait for the Pod to indicate Ready == True.
88+
watcher, err := coreclient.Pods(namespace).Watch(
89+
metav1.SingleObject(pod.ObjectMeta),
90+
)
91+
if err != nil {
92+
panic(err)
93+
}
94+
95+
for event := range watcher.ResultChan() {
96+
switch event.Type {
97+
case watch.Modified:
98+
pod = event.Object.(*corev1.Pod)
99+
100+
// If the Pod contains a status condition Ready == True, stop
101+
// watching.
102+
for _, cond := range pod.Status.Conditions {
103+
if cond.Type == corev1.PodReady &&
104+
cond.Status == corev1.ConditionTrue {
105+
watcher.Stop()
106+
}
107+
}
108+
109+
default:
110+
panic("unexpected event type " + event.Type)
111+
}
112+
}
113+
114+
// Prepare the API URL used to execute another process within the Pod. In
115+
// this case, we'll run a remote shell.
116+
req := coreclient.RESTClient().
117+
Post().
118+
Namespace(pod.Namespace).
119+
Resource("pods").
120+
Name(pod.Name).
121+
SubResource("exec").
122+
VersionedParams(&corev1.PodExecOptions{
123+
Container: pod.Spec.Containers[0].Name,
124+
Command: []string{"/bin/sh"},
125+
Stdin: true,
126+
Stdout: true,
127+
Stderr: true,
128+
TTY: true,
129+
}, scheme.ParameterCodec)
130+
131+
exec, err := remotecommand.NewSPDYExecutor(restconfig, "POST", req.URL())
132+
if err != nil {
133+
panic(err)
134+
}
135+
136+
// Put the terminal into raw mode to prevent it echoing characters twice.
137+
oldState, err := terminal.MakeRaw(0)
138+
if err != nil {
139+
panic(err)
140+
}
141+
defer terminal.Restore(0, oldState)
142+
143+
// Connect this process' std{in,out,err} to the remote shell process.
144+
err = exec.Stream(remotecommand.StreamOptions{
145+
Stdin: os.Stdin,
146+
Stdout: os.Stdout,
147+
Stderr: os.Stderr,
148+
Tty: true,
149+
})
150+
if err != nil {
151+
panic(err)
152+
}
153+
154+
fmt.Println()
155+
}
156+
----

0 commit comments

Comments
 (0)