Skip to content

Commit

Permalink
Add create, update and delete protocol buffer methods.
Browse files Browse the repository at this point in the history
  • Loading branch information
brendanburns committed Sep 11, 2017
1 parent 88e63d4 commit 43e1a55
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
import io.kubernetes.client.ApiException;
import io.kubernetes.client.Configuration;
import io.kubernetes.client.ProtoClient;
import io.kubernetes.client.ProtoClient.ObjectOrStatus;
import io.kubernetes.client.proto.Meta.ObjectMeta;
import io.kubernetes.client.proto.V1.Namespace;
import io.kubernetes.client.proto.V1.NamespaceSpec;
import io.kubernetes.client.proto.V1.Pod;
import io.kubernetes.client.proto.V1.PodList;
import io.kubernetes.client.util.Config;
Expand All @@ -37,11 +41,36 @@ public static void main(String[] args) throws IOException, ApiException, Interru
Configuration.setDefaultApiClient(client);

ProtoClient pc = new ProtoClient(client);
PodList list = pc.list(PodList.newBuilder(), "/api/v1/namespaces/default/pods");
ObjectOrStatus<PodList> list = pc.list(PodList.newBuilder(), "/api/v1/namespaces/default/pods");

if (list.getItemsCount() > 0) {
Pod p = list.getItems(0);
System.out.println(p.toString());
if (list.object.getItemsCount() > 0) {
Pod p = list.object.getItems(0);
System.out.println(p);
}

Namespace namespace = Namespace.newBuilder()
.setMetadata(ObjectMeta.newBuilder()
.setName("test").build())
.build();

ObjectOrStatus<Namespace> ns = pc.create(namespace, "/api/v1/namespaces", "v1", "Namespace");
System.out.println(ns);
if (ns.object != null) {
namespace = ns.object.toBuilder()
.setSpec(
NamespaceSpec.newBuilder()
.addFinalizers("test")
.build()
)
.build();
// This is how you would update an object, but you can't actually
// update namespaces, so this returns a 405
ns = pc.update(namespace, "/api/v1/namespaces/test", "v1", "Namespace");
System.out.println(ns.status);
}

ns = pc.delete(Namespace.newBuilder(), "/api/v1/namespaces/test");
System.out.println(ns);

}
}
127 changes: 108 additions & 19 deletions util/src/main/java/io/kubernetes/client/ProtoClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,58 @@

import io.kubernetes.client.ApiException;
import io.kubernetes.client.Configuration;
import io.kubernetes.client.models.V1ObjectMeta;
import io.kubernetes.client.proto.Meta.Status;
import io.kubernetes.client.proto.Runtime.TypeMeta;
import io.kubernetes.client.proto.Runtime.Unknown;

import com.google.common.io.ByteStreams;
import com.google.common.primitives.Bytes;
import com.google.protobuf.Descriptors;
import com.google.protobuf.Message;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.ResponseBody;
import okio.ByteString;

import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;

public class ProtoClient {
/**
* ObjectOrStatus is an object that is the return from a method call
* it holds either an API Object or an API Status object, but not both.
* Only one field may be non-null at a time.
*
* Oh, how I long for multi-return...
*/
public static class ObjectOrStatus<T extends Message> {
public ObjectOrStatus(T obj, Status status) {
this.object = obj;
this.status = status;
}

public T object;
public Status status;

public String toString() {
if (object != null) {
return object.toString();
}
return status.toString();
}
}

private ApiClient apiClient;
// Magic number for the beginning of proto encoded.
// https://github.com/kubernetes/apimachinery/blob/master/pkg/runtime/serializer/protobuf/protobuf.go#L42
private static final byte[] MAGIC = new byte[] { 0x6b, 0x38, 0x73, 0x00 };
private static final String MEDIA_TYPE = "application/vnd.kubernetes.protobuf";

/**
* Simple Protocol Budder API client constructor, uses default configuration
Expand Down Expand Up @@ -58,8 +92,8 @@ public void setApiClient(ApiClient apiClient) {
* @param path The URL path to call (e.g. /api/v1/namespaces/default/pods/pod-name)
* @return A Message of type T
*/
public <T extends Message> T get(T.Builder builder, String path) throws ApiException, IOException {
return (T) request(builder, path, "GET");
public <T extends Message> ObjectOrStatus<T> get(T.Builder builder, String path) throws ApiException, IOException {
return request(builder, path, "GET", null, null, null);
}

/**
Expand All @@ -69,10 +103,46 @@ public <T extends Message> T get(T.Builder builder, String path) throws ApiExcep
* @param path The URL path to call (e.g. /api/v1/namespaces/default/pods/pod-name)
* @return A Message of type T
*/
public <T extends Message> T list(T.Builder listObj, String path) throws ApiException, IOException {
public <T extends Message> ObjectOrStatus<T> list(T.Builder listObj, String path) throws ApiException, IOException {
return get(listObj, path);
}

/**
* Create a Kubernetes API object using protocol buffer encoding. Performs a POST
* @param obj The object to create
* @param path The URL path to call
* @param apiVersion The api version to use
* @param kind The kind of the object
* @return The response received.
*/
public <T extends Message> ObjectOrStatus<T> create(T obj, String path, String apiVersion, String kind)
throws ApiException, IOException {
return request(obj.newBuilderForType(), path, "POST", obj, apiVersion, kind);
}

/**
* Update a Kubernetes API object using protocol buffer encoding. Performs a PUT
* @param obj The object to create
* @param path The URL path to call
* @param apiVersion The api version to use
* @param kind The kind of the object
* @return The response received.
*/
public <T extends Message> ObjectOrStatus<T> update(T obj, String path, String apiVersion, String kind)
throws ApiException, IOException {
return request(obj.newBuilderForType(), path, "PUT", obj, apiVersion, kind);
}

/**
* Delete a kubernetes API object using protocol buffer encoding.
* @param builder The builder for the response
* @param path The path to call in the API server
* @return The response received
*/
public <T extends Message> ObjectOrStatus<T> delete(T.Builder builder, String path) throws ApiException, IOException {
return request(builder, path, "DELETE", null, null, null);
}

/**
* Generic protocol buffer based HTTP request.
* Not intended for general consumption, but public for advance use cases.
Expand All @@ -81,31 +151,50 @@ public <T extends Message> T list(T.Builder listObj, String path) throws ApiExce
* @param path The URL path to call (e.g. /api/v1/namespaces/default/pods/pod-name)
* @return A Message of type T
*/
public <T extends Message> T request(T.Builder builder, String path, String method) throws ApiException, IOException {
public <T extends Message> ObjectOrStatus<T> request(T.Builder builder, String path, String method, T body, String apiVersion,
String kind) throws ApiException, IOException {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Content-type", "application/vnd.kubernetes.protobuf");
headers.put("Accept", "application/vnd.kubernetes.protobuf");
headers.put("Content-type", MEDIA_TYPE);
headers.put("Accept", MEDIA_TYPE);
Request request = apiClient.buildRequest(path, method, new ArrayList<Pair>(), new ArrayList<Pair>(), null,
headers, new HashMap<String, Object>(), new String[0], null);
if (body != null) {
byte[] bytes = encode(body, apiVersion, kind);
request = request.newBuilder().post(RequestBody.create(MediaType.parse(MEDIA_TYPE), bytes)).build();
}
Response resp = apiClient.getHttpClient().newCall(request).execute();
Unknown u = parse(resp.body().byteStream());
return (T) builder.mergeFrom(u.getRaw()).build();
if (u.getTypeMeta().getApiVersion().equals("v1") &&
u.getTypeMeta().getKind().equals("Status")) {
Status status = Status.newBuilder().mergeFrom(u.getRaw()).build();
return new ObjectOrStatus(null, status);
}

return new ObjectOrStatus((T) builder.mergeFrom(u.getRaw()).build(), null);
}

// This isn't really documented anywhere except the code, but
// the proto-buf format is:
// * 4 byte magic number
// * Protocol Buffer encoded object of type runtime.Unknown
// * the 'raw' field in that object contains a Protocol Buffer
// encoding of the actual object.
// TODO: Document this somewhere proper.

private byte[] encode(Message msg, String apiVersion, String kind) {
// It is unfortunate that we have to include apiVersion and kind,
// since we should be able to extract it from the Message, but
// for now at least, those fields are missing from the proto-buffer.
Unknown u = Unknown.newBuilder().setTypeMeta(TypeMeta.newBuilder().setApiVersion(apiVersion).setKind(kind))
.setRaw(msg.toByteString()).build();
return Bytes.concat(MAGIC, u.toByteArray());
}

private Unknown parse(InputStream stream) throws ApiException, IOException {
// This isn't really documented anywhere except the code, but
// the proto-buf format is:
// * 4 byte magic number
// * Protocol Buffer encoded object of type runtime.Unknown
// * the 'raw' field in that object contains a Protocol Buffer
// encoding of the actual object.
// TODO: Document this somewhere proper.
byte[] magic = new byte[4];
stream.read(magic);
for (int i = 0; i < MAGIC.length; i++) {
if (magic[i] != MAGIC[i]) {
throw new ApiException("Unexpected magic number: " + magic);
}
ByteStreams.readFully(stream, magic);
if (!Arrays.equals(magic, MAGIC)) {
throw new ApiException("Unexpected magic number: " + magic);
}
return Unknown.parseFrom(stream);
}
Expand Down

0 comments on commit 43e1a55

Please sign in to comment.