Skip to content

Commit dd979d0

Browse files
brendandburnsmbohlool
authored andcommitted
Add create, update and delete protocol buffer methods. (#71)
* Add create, update and delete protocol buffer methods. * Add create, update and delete protocol buffer methods.
1 parent 88e63d4 commit dd979d0

File tree

2 files changed

+148
-26
lines changed

2 files changed

+148
-26
lines changed

examples/src/main/java/io/kubernetes/client/examples/ProtoExample.java

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616
import io.kubernetes.client.ApiException;
1717
import io.kubernetes.client.Configuration;
1818
import io.kubernetes.client.ProtoClient;
19+
import io.kubernetes.client.ProtoClient.ObjectOrStatus;
20+
import io.kubernetes.client.proto.Meta.ObjectMeta;
21+
import io.kubernetes.client.proto.Meta.Status;
22+
import io.kubernetes.client.proto.V1.Namespace;
23+
import io.kubernetes.client.proto.V1.NamespaceSpec;
1924
import io.kubernetes.client.proto.V1.Pod;
2025
import io.kubernetes.client.proto.V1.PodList;
2126
import io.kubernetes.client.util.Config;
@@ -37,11 +42,36 @@ public static void main(String[] args) throws IOException, ApiException, Interru
3742
Configuration.setDefaultApiClient(client);
3843

3944
ProtoClient pc = new ProtoClient(client);
40-
PodList list = pc.list(PodList.newBuilder(), "/api/v1/namespaces/default/pods");
45+
ObjectOrStatus<PodList> list = pc.list(PodList.newBuilder(), "/api/v1/namespaces/default/pods");
4146

42-
if (list.getItemsCount() > 0) {
43-
Pod p = list.getItems(0);
44-
System.out.println(p.toString());
47+
if (list.object.getItemsCount() > 0) {
48+
Pod p = list.object.getItems(0);
49+
System.out.println(p);
4550
}
51+
52+
Namespace namespace = Namespace.newBuilder()
53+
.setMetadata(ObjectMeta.newBuilder()
54+
.setName("test").build())
55+
.build();
56+
57+
ObjectOrStatus<Namespace> ns = pc.create(namespace, "/api/v1/namespaces", "v1", "Namespace");
58+
System.out.println(ns);
59+
if (ns.object != null) {
60+
namespace = ns.object.toBuilder()
61+
.setSpec(
62+
NamespaceSpec.newBuilder()
63+
.addFinalizers("test")
64+
.build()
65+
)
66+
.build();
67+
// This is how you would update an object, but you can't actually
68+
// update namespaces, so this returns a 405
69+
ns = pc.update(namespace, "/api/v1/namespaces/test", "v1", "Namespace");
70+
System.out.println(ns.status);
71+
}
72+
73+
Status stat = pc.delete(Namespace.newBuilder(), "/api/v1/namespaces/test");
74+
System.out.println(stat);
75+
4676
}
4777
}

util/src/main/java/io/kubernetes/client/ProtoClient.java

Lines changed: 114 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,58 @@
22

33
import io.kubernetes.client.ApiException;
44
import io.kubernetes.client.Configuration;
5+
import io.kubernetes.client.models.V1ObjectMeta;
6+
import io.kubernetes.client.proto.Meta.Status;
7+
import io.kubernetes.client.proto.Runtime.TypeMeta;
58
import io.kubernetes.client.proto.Runtime.Unknown;
69

710
import com.google.common.io.ByteStreams;
11+
import com.google.common.primitives.Bytes;
12+
import com.google.protobuf.Descriptors;
813
import com.google.protobuf.Message;
14+
import com.squareup.okhttp.MediaType;
915
import com.squareup.okhttp.Request;
16+
import com.squareup.okhttp.RequestBody;
1017
import com.squareup.okhttp.Response;
1118
import com.squareup.okhttp.ResponseBody;
19+
import okio.ByteString;
1220

1321
import java.io.IOException;
1422
import java.io.InputStream;
23+
import java.util.Arrays;
1524
import java.util.ArrayList;
1625
import java.util.HashMap;
1726

1827
public class ProtoClient {
28+
/**
29+
* ObjectOrStatus is an object that is the return from a method call
30+
* it holds either an API Object or an API Status object, but not both.
31+
* Only one field may be non-null at a time.
32+
*
33+
* Oh, how I long for multi-return...
34+
*/
35+
public static class ObjectOrStatus<T extends Message> {
36+
public ObjectOrStatus(T obj, Status status) {
37+
this.object = obj;
38+
this.status = status;
39+
}
40+
41+
public T object;
42+
public Status status;
43+
44+
public String toString() {
45+
if (object != null) {
46+
return object.toString();
47+
}
48+
return status.toString();
49+
}
50+
}
51+
1952
private ApiClient apiClient;
2053
// Magic number for the beginning of proto encoded.
2154
// https://github.com/kubernetes/apimachinery/blob/master/pkg/runtime/serializer/protobuf/protobuf.go#L42
2255
private static final byte[] MAGIC = new byte[] { 0x6b, 0x38, 0x73, 0x00 };
56+
private static final String MEDIA_TYPE = "application/vnd.kubernetes.protobuf";
2357

2458
/**
2559
* Simple Protocol Budder API client constructor, uses default configuration
@@ -56,56 +90,114 @@ public void setApiClient(ApiClient apiClient) {
5690
* Get a Kubernetes API object using protocol buffer encoding.
5791
* @param builder The appropriate Builder for the object receveived from the request.
5892
* @param path The URL path to call (e.g. /api/v1/namespaces/default/pods/pod-name)
59-
* @return A Message of type T
93+
* @return An ObjectOrStatus which contains the Object requested, or a Status about the request.
6094
*/
61-
public <T extends Message> T get(T.Builder builder, String path) throws ApiException, IOException {
62-
return (T) request(builder, path, "GET");
95+
public <T extends Message> ObjectOrStatus<T> get(T.Builder builder, String path) throws ApiException, IOException {
96+
return request(builder, path, "GET", null, null, null);
6397
}
6498

6599
/**
66100
* List is fluent, semantic sugar method on top of get, which is intended
67101
* to convey that the object is a List of objects rather than a single object
68102
* @param builder The appropriate Builder for the object receveived from the request.
69103
* @param path The URL path to call (e.g. /api/v1/namespaces/default/pods/pod-name)
70-
* @return A Message of type T
104+
* @return An ObjectOrStatus which contains the Object requested, or a Status about the request.
71105
*/
72-
public <T extends Message> T list(T.Builder listObj, String path) throws ApiException, IOException {
106+
public <T extends Message> ObjectOrStatus<T> list(T.Builder listObj, String path) throws ApiException, IOException {
73107
return get(listObj, path);
74108
}
75109

110+
/**
111+
* Create a Kubernetes API object using protocol buffer encoding. Performs a POST
112+
* @param obj The object to create
113+
* @param path The URL path to call
114+
* @param apiVersion The api version to use
115+
* @param kind The kind of the object
116+
* @return An ObjectOrStatus which contains the Object requested, or a Status about the request.
117+
*/
118+
public <T extends Message> ObjectOrStatus<T> create(T obj, String path, String apiVersion, String kind)
119+
throws ApiException, IOException {
120+
return request(obj.newBuilderForType(), path, "POST", obj, apiVersion, kind);
121+
}
122+
123+
/**
124+
* Update a Kubernetes API object using protocol buffer encoding. Performs a PUT
125+
* @param obj The object to create
126+
* @param path The URL path to call
127+
* @param apiVersion The api version to use
128+
* @param kind The kind of the object
129+
* @return An ObjectOrStatus which contains the Object requested, or a Status about the request.
130+
*/
131+
public <T extends Message> ObjectOrStatus<T> update(T obj, String path, String apiVersion, String kind)
132+
throws ApiException, IOException {
133+
return request(obj.newBuilderForType(), path, "PUT", obj, apiVersion, kind);
134+
}
135+
136+
/**
137+
* Delete a kubernetes API object using protocol buffer encoding.
138+
* @param builder The builder for the response
139+
* @param path The path to call in the API server
140+
* @return The response status
141+
*/
142+
public <T extends Message> Status delete(T.Builder builder, String path) throws ApiException, IOException {
143+
return request(builder, path, "DELETE", null, null, null).status;
144+
}
145+
76146
/**
77147
* Generic protocol buffer based HTTP request.
78148
* Not intended for general consumption, but public for advance use cases.
79149
* @param builder The appropriate Builder for the object receveived from the request.
80150
* @param method The HTTP method (e.g. GET) for this request.
81151
* @param path The URL path to call (e.g. /api/v1/namespaces/default/pods/pod-name)
82-
* @return A Message of type T
152+
* @param body The body to send with the request (optional)
153+
* @param apiVersion The 'apiVersion' to use when encoding, required if body is non-null, ignored otherwise.
154+
* @param kind The 'kind' field to use when encoding, required if body is non-null, ignored otherwise.
155+
* @return An ObjectOrStatus which contains the Object requested, or a Status about the request.
83156
*/
84-
public <T extends Message> T request(T.Builder builder, String path, String method) throws ApiException, IOException {
157+
public <T extends Message> ObjectOrStatus<T> request(T.Builder builder, String path, String method, T body, String apiVersion,
158+
String kind) throws ApiException, IOException {
85159
HashMap<String, String> headers = new HashMap<String, String>();
86-
headers.put("Content-type", "application/vnd.kubernetes.protobuf");
87-
headers.put("Accept", "application/vnd.kubernetes.protobuf");
160+
headers.put("Content-type", MEDIA_TYPE);
161+
headers.put("Accept", MEDIA_TYPE);
88162
Request request = apiClient.buildRequest(path, method, new ArrayList<Pair>(), new ArrayList<Pair>(), null,
89163
headers, new HashMap<String, Object>(), new String[0], null);
164+
if (body != null) {
165+
byte[] bytes = encode(body, apiVersion, kind);
166+
request = request.newBuilder().post(RequestBody.create(MediaType.parse(MEDIA_TYPE), bytes)).build();
167+
}
90168
Response resp = apiClient.getHttpClient().newCall(request).execute();
91169
Unknown u = parse(resp.body().byteStream());
92-
return (T) builder.mergeFrom(u.getRaw()).build();
170+
if (u.getTypeMeta().getApiVersion().equals("v1") &&
171+
u.getTypeMeta().getKind().equals("Status")) {
172+
Status status = Status.newBuilder().mergeFrom(u.getRaw()).build();
173+
return new ObjectOrStatus(null, status);
174+
}
175+
176+
return new ObjectOrStatus((T) builder.mergeFrom(u.getRaw()).build(), null);
177+
}
178+
179+
// This isn't really documented anywhere except the code, but
180+
// the proto-buf format is:
181+
// * 4 byte magic number
182+
// * Protocol Buffer encoded object of type runtime.Unknown
183+
// * the 'raw' field in that object contains a Protocol Buffer
184+
// encoding of the actual object.
185+
// TODO: Document this somewhere proper.
186+
187+
private byte[] encode(Message msg, String apiVersion, String kind) {
188+
// It is unfortunate that we have to include apiVersion and kind,
189+
// since we should be able to extract it from the Message, but
190+
// for now at least, those fields are missing from the proto-buffer.
191+
Unknown u = Unknown.newBuilder().setTypeMeta(TypeMeta.newBuilder().setApiVersion(apiVersion).setKind(kind))
192+
.setRaw(msg.toByteString()).build();
193+
return Bytes.concat(MAGIC, u.toByteArray());
93194
}
94195

95196
private Unknown parse(InputStream stream) throws ApiException, IOException {
96-
// This isn't really documented anywhere except the code, but
97-
// the proto-buf format is:
98-
// * 4 byte magic number
99-
// * Protocol Buffer encoded object of type runtime.Unknown
100-
// * the 'raw' field in that object contains a Protocol Buffer
101-
// encoding of the actual object.
102-
// TODO: Document this somewhere proper.
103197
byte[] magic = new byte[4];
104-
stream.read(magic);
105-
for (int i = 0; i < MAGIC.length; i++) {
106-
if (magic[i] != MAGIC[i]) {
107-
throw new ApiException("Unexpected magic number: " + magic);
108-
}
198+
ByteStreams.readFully(stream, magic);
199+
if (!Arrays.equals(magic, MAGIC)) {
200+
throw new ApiException("Unexpected magic number: " + magic);
109201
}
110202
return Unknown.parseFrom(stream);
111203
}

0 commit comments

Comments
 (0)