diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java
index 6d033688d8c2..300d38908a8b 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Compute.java
@@ -869,6 +869,54 @@ public static SnapshotFilter notEquals(SnapshotField field, long value) {
}
}
+ /**
+ * Class for filtering image lists.
+ */
+ class ImageFilter extends ListFilter {
+
+ private static final long serialVersionUID = -3601427417234098397L;
+
+ private ImageFilter(ImageField field, ComparisonOperator operator, Object value) {
+ super(field.selector(), operator, value);
+ }
+
+ /**
+ * Returns an equals filter for the given field and string value. For string fields,
+ * {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
+ * match the entire field.
+ *
+ * @see RE2
+ */
+ public static ImageFilter equals(ImageField field, String value) {
+ return new ImageFilter(checkNotNull(field), ComparisonOperator.EQ, checkNotNull(value));
+ }
+
+ /**
+ * Returns a not-equals filter for the given field and string value. For string fields,
+ * {@code value} is interpreted as a regular expression using RE2 syntax. {@code value} must
+ * match the entire field.
+ *
+ * @see RE2
+ */
+ public static ImageFilter notEquals(ImageField field, String value) {
+ return new ImageFilter(checkNotNull(field), ComparisonOperator.NE, checkNotNull(value));
+ }
+
+ /**
+ * Returns an equals filter for the given field and long value.
+ */
+ public static ImageFilter equals(ImageField field, long value) {
+ return new ImageFilter(checkNotNull(field), ComparisonOperator.EQ, value);
+ }
+
+ /**
+ * Returns a not-equals filter for the given field and long value.
+ */
+ public static ImageFilter notEquals(ImageField field, long value) {
+ return new ImageFilter(checkNotNull(field), ComparisonOperator.NE, value);
+ }
+ }
+
/**
* Class for specifying disk type get options.
*/
@@ -1469,6 +1517,74 @@ public static SnapshotListOption fields(SnapshotField... fields) {
}
}
+ /**
+ * Class for specifying image get options.
+ */
+ class ImageOption extends Option {
+
+ private static final long serialVersionUID = -7622190783089299272L;
+
+ private ImageOption(ComputeRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify the image's fields to be returned by the RPC call. If this
+ * option is not provided, all image's fields are returned. {@code ImageOption.fields} can be
+ * used to specify only the fields of interest. {@link Image#imageId()} and
+ * {@link Image#configuration()} are always returned, even if not specified.
+ */
+ public static ImageOption fields(ImageField... fields) {
+ return new ImageOption(ComputeRpc.Option.FIELDS, ImageField.selector(fields));
+ }
+ }
+
+ /**
+ * Class for specifying image list options.
+ */
+ class ImageListOption extends Option {
+
+ private static final long serialVersionUID = -4927977224287915654L;
+
+ private ImageListOption(ComputeRpc.Option option, Object value) {
+ super(option, value);
+ }
+
+ /**
+ * Returns an option to specify a filter on the images being listed.
+ */
+ public static ImageListOption filter(ImageFilter filter) {
+ return new ImageListOption(ComputeRpc.Option.FILTER, filter.toPb());
+ }
+
+ /**
+ * Returns an option to specify the maximum number of images returned per page. {@code pageSize}
+ * must be between 0 and 500 (inclusive). If not specified 500 is used.
+ */
+ public static ImageListOption pageSize(long pageSize) {
+ return new ImageListOption(ComputeRpc.Option.MAX_RESULTS, pageSize);
+ }
+
+ /**
+ * Returns an option to specify the page token from which to start listing images.
+ */
+ public static ImageListOption pageToken(String pageToken) {
+ return new ImageListOption(ComputeRpc.Option.PAGE_TOKEN, pageToken);
+ }
+
+ /**
+ * Returns an option to specify the image's fields to be returned by the RPC call. If this
+ * option is not provided, all image's fields are returned. {@code ImageListOption.fields} can
+ * be used to specify only the fields of interest. {@link Image#imageId()} and
+ * {@link Image#configuration()} are always returned, even if not specified.
+ */
+ public static ImageListOption fields(ImageField... fields) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("items(").append(ImageField.selector(fields)).append("),nextPageToken");
+ return new ImageListOption(ComputeRpc.Option.FIELDS, builder.toString());
+ }
+ }
+
/**
* Returns the requested disk type or {@code null} if not found.
*
@@ -1699,4 +1815,57 @@ public static SnapshotListOption fields(SnapshotField... fields) {
* Deleting a snapshot
*/
Operation deleteSnapshot(String snapshot, OperationOption... options);
+
+ /**
+ * Creates a new image.
+ *
+ * @return a global operation for image's creation
+ * @throws ComputeException upon failure
+ */
+ Operation create(ImageInfo image, OperationOption... options);
+
+ /**
+ * Returns the requested image or {@code null} if not found.
+ *
+ * @throws ComputeException upon failure
+ */
+ Image get(ImageId imageId, ImageOption... options);
+
+ /**
+ * Lists images in the provided project that are available to the current user. This method can be
+ * used to list publicly-available images by providing the respective image project. Examples of
+ * image projects are: {@code centos-cloud}, {@code coreos-cloud}, {@code debian-cloud},
+ * {@code opensuse-cloud}, {@code rhel-cloud}, {@code suse-cloud}, {@code ubuntu-os-cloud} and
+ * {@code windows-cloud}. Attempting to delete or deprecate a publicly-available image will fail.
+ *
+ * @throws ComputeException upon failure
+ * @see Operating Systems
+ */
+ Page listImages(String project, ImageListOption... options);
+
+ /**
+ * Lists images in the current project.
+ *
+ * @throws ComputeException upon failure
+ */
+ Page listImages(ImageListOption... options);
+
+ /**
+ * Deletes the requested image.
+ *
+ * @return a global operation if the delete request was issued correctly, {@code null} if the
+ * image was not found
+ * @throws ComputeException upon failure or if {@code image} is a publicly-available image
+ */
+ Operation delete(ImageId image, OperationOption... options);
+
+ /**
+ * Deprecates the requested image.
+ *
+ * @return a global operation if the deprecation request was issued correctly, {@code null} if the
+ * image was not found
+ * @throws ComputeException upon failure or if {@code image} is a publicly-available image
+ */
+ Operation deprecate(ImageId image, DeprecationStatus deprecationStatus,
+ OperationOption... options);
}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java
index a348e1e0e253..1f875ad337f1 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/ComputeImpl.java
@@ -292,6 +292,27 @@ public Page nextPage() {
}
}
+ private static class ImagePageFetcher implements NextPageFetcher {
+
+ private static final long serialVersionUID = 6403679803137922023L;
+ private final Map requestOptions;
+ private final ComputeOptions serviceOptions;
+ private final String project;
+
+ ImagePageFetcher(String project, ComputeOptions serviceOptions, String cursor,
+ Map optionMap) {
+ this.requestOptions =
+ PageImpl.nextRequestOptions(ComputeRpc.Option.PAGE_TOKEN, cursor, optionMap);
+ this.serviceOptions = serviceOptions;
+ this.project = project;
+ }
+
+ @Override
+ public Page nextPage() {
+ return listImages(project, serviceOptions, requestOptions);
+ }
+ }
+
private final ComputeRpc computeRpc;
ComputeImpl(ComputeOptions options) {
@@ -1026,6 +1047,120 @@ public com.google.api.services.compute.model.Operation call() {
}
}
+ @Override
+ public Operation create(ImageInfo image, OperationOption... options) {
+ final ImageInfo completeImage = image.setProjectId(options().projectId());
+ final Map optionsMap = optionMap(options);
+ try {
+ com.google.api.services.compute.model.Operation answer =
+ runWithRetries(new Callable() {
+ @Override
+ public com.google.api.services.compute.model.Operation call() {
+ return computeRpc.createImage(completeImage.toPb(), optionsMap);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : Operation.fromPb(this, answer);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Image get(ImageId imageId, ImageOption... options) {
+ final ImageId completeImageId = imageId.setProjectId(options().projectId());
+ final Map optionsMap = optionMap(options);
+ try {
+ com.google.api.services.compute.model.Image answer =
+ runWithRetries(new Callable() {
+ @Override
+ public com.google.api.services.compute.model.Image call() {
+ return computeRpc.getImage(completeImageId.project(), completeImageId.image(),
+ optionsMap);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : Image.fromPb(this, answer);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Page listImages(String project, ImageListOption... options) {
+ return listImages(project, options(), optionMap(options));
+ }
+
+ @Override
+ public Page listImages(ImageListOption... options) {
+ return listImages(options().projectId(), options(), optionMap(options));
+ }
+
+ private static Page listImages(final String project, final ComputeOptions serviceOptions,
+ final Map optionsMap) {
+ try {
+ ComputeRpc.Tuple> result =
+ runWithRetries(new Callable>>() {
+ @Override
+ public ComputeRpc.Tuple> call() {
+ return serviceOptions.rpc().listImages(project, optionsMap);
+ }
+ }, serviceOptions.retryParams(), EXCEPTION_HANDLER);
+ String cursor = result.x();
+ Iterable images = Iterables.transform(
+ result.y() == null ? ImmutableList.of()
+ : result.y(),
+ new Function() {
+ @Override
+ public Image apply(com.google.api.services.compute.model.Image image) {
+ return Image.fromPb(serviceOptions.service(), image);
+ }
+ });
+ return new PageImpl<>(new ImagePageFetcher(project, serviceOptions, cursor, optionsMap),
+ cursor, images);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Operation delete(ImageId image, OperationOption... options) {
+ final ImageId completeId = image.setProjectId(options().projectId());
+ final Map optionsMap = optionMap(options);
+ try {
+ com.google.api.services.compute.model.Operation answer =
+ runWithRetries(new Callable() {
+ @Override
+ public com.google.api.services.compute.model.Operation call() {
+ return computeRpc.deleteImage(completeId.project(), completeId.image(), optionsMap);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : Operation.fromPb(this, answer);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
+ @Override
+ public Operation deprecate(ImageId image,
+ final DeprecationStatus deprecationStatus, OperationOption... options) {
+ final ImageId completeId = image.setProjectId(options().projectId());
+ final Map optionsMap = optionMap(options);
+ try {
+ com.google.api.services.compute.model.Operation answer =
+ runWithRetries(new Callable() {
+ @Override
+ public com.google.api.services.compute.model.Operation call() {
+ return computeRpc.deprecateImage(completeId.project(), completeId.image(),
+ deprecationStatus.toPb(), optionsMap);
+ }
+ }, options().retryParams(), EXCEPTION_HANDLER);
+ return answer == null ? null : Operation.fromPb(this, answer);
+ } catch (RetryHelper.RetryHelperException e) {
+ throw ComputeException.translateAndThrow(e);
+ }
+ }
+
private Map optionMap(Option... options) {
Map optionMap = Maps.newEnumMap(ComputeRpc.Option.class);
for (Option option : options) {
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java
new file mode 100644
index 000000000000..7815929f3bf3
--- /dev/null
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/compute/Image.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2016 Google Inc. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.gcloud.compute;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.gcloud.compute.Compute.ImageOption;
+import com.google.gcloud.compute.Compute.OperationOption;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A Google Compute Engine Image. An image contains a boot loader, an operating system and a root
+ * file system that is necessary for starting an instance. Compute Engine offers publicly-available
+ * images of certain operating systems that you can use, or you can create a custom image. A custom
+ * image is an image created from one of your virtual machine instances that contains your specific
+ * instance configurations. To get an {@code Image} object with the most recent information use
+ * {@link #reload}. {@code Image} adds a layer of service-related functionality
+ * over {@link ImageInfo}.
+ *
+ * @see Images
+ */
+public class Image extends ImageInfo {
+
+ private static final long serialVersionUID = 4623766590317494020L;
+
+ private final ComputeOptions options;
+ private transient Compute compute;
+
+ /**
+ * A builder for {@code Image} objects.
+ */
+ public static class Builder extends ImageInfo.Builder {
+
+ private final Compute compute;
+ private final ImageInfo.BuilderImpl infoBuilder;
+
+ Builder(Compute compute, ImageId imageId, ImageConfiguration configuration) {
+ this.compute = compute;
+ this.infoBuilder = new ImageInfo.BuilderImpl();
+ this.infoBuilder.imageId(imageId);
+ this.infoBuilder.configuration(configuration);
+ }
+
+ Builder(Image image) {
+ this.compute = image.compute;
+ this.infoBuilder = new ImageInfo.BuilderImpl(image);
+ }
+
+ @Override
+ Builder id(String id) {
+ infoBuilder.id(id);
+ return this;
+ }
+
+ @Override
+ Builder creationTimestamp(Long creationTimestamp) {
+ infoBuilder.creationTimestamp(creationTimestamp);
+ return this;
+ }
+
+ @Override
+ public Builder imageId(ImageId imageId) {
+ infoBuilder.imageId(imageId);
+ return this;
+ }
+
+ @Override
+ public Builder description(String description) {
+ infoBuilder.description(description);
+ return this;
+ }
+
+ @Override
+ public Builder configuration(ImageConfiguration configuration) {
+ infoBuilder.configuration(configuration);
+ return this;
+ }
+
+ @Override
+ Builder status(Status status) {
+ infoBuilder.status(status);
+ return this;
+ }
+
+ @Override
+ Builder diskSizeGb(Long diskSizeGb) {
+ infoBuilder.diskSizeGb(diskSizeGb);
+ return this;
+ }
+
+ @Override
+ Builder licenses(List licenses) {
+ infoBuilder.licenses(licenses);
+ return this;
+ }
+
+ @Override
+ Builder deprecationStatus(DeprecationStatus deprecationStatus) {
+ infoBuilder.deprecationStatus(deprecationStatus);
+ return this;
+ }
+
+ @Override
+ public Image build() {
+ return new Image(compute, infoBuilder);
+ }
+ }
+
+ Image(Compute compute, ImageInfo.BuilderImpl infoBuilder) {
+ super(infoBuilder);
+ this.compute = checkNotNull(compute);
+ this.options = compute.options();
+ }
+
+ /**
+ * Checks if this image exists.
+ *
+ * @return {@code true} if this image exists, {@code false} otherwise
+ * @throws ComputeException upon failure
+ */
+ public boolean exists() {
+ return reload(ImageOption.fields()) != null;
+ }
+
+ /**
+ * Fetches current image' latest information. Returns {@code null} if the image does not exist.
+ *
+ * @param options image options
+ * @return an {@code Image} object with latest information or {@code null} if not found
+ * @throws ComputeException upon failure
+ */
+ public Image reload(ImageOption... options) {
+ return compute.get(imageId(), options);
+ }
+
+ /**
+ * Deletes this image.
+ *
+ * @return a global operation if the delete request was successfully sent, {@code null} if the
+ * image was not found
+ * @throws ComputeException upon failure or if this image is a publicly-available image
+ */
+ public Operation delete(OperationOption... options) {
+ return compute.delete(imageId(), options);
+ }
+
+ /**
+ * Deprecates this image.
+ *
+ * @return a global operation if the deprecation request was successfully sent, {@code null} if
+ * the image was not found
+ * @throws ComputeException upon failure or if this image is a publicly-available image
+ */
+ public Operation deprecate(DeprecationStatus deprecationStatus,
+ OperationOption... options) {
+ return compute.deprecate(imageId(), deprecationStatus, options);
+ }
+
+ /**
+ * Returns the image's {@code Compute} object used to issue requests.
+ */
+ public Compute compute() {
+ return compute;
+ }
+
+ @Override
+ public Builder toBuilder() {
+ return new Builder(this);
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ return obj instanceof Image
+ && Objects.equals(toPb(), ((Image) obj).toPb())
+ && Objects.equals(options, ((Image) obj).options);
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(super.hashCode(), options);
+ }
+
+ private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
+ input.defaultReadObject();
+ this.compute = options.service();
+ }
+
+ static Image fromPb(Compute compute, com.google.api.services.compute.model.Image imagePb) {
+ return new Image(compute, new ImageInfo.BuilderImpl(imagePb));
+ }
+}
diff --git a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java
index ff0831ece6e9..523686283db0 100644
--- a/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java
+++ b/gcloud-java-compute/src/main/java/com/google/gcloud/spi/ComputeRpc.java
@@ -17,7 +17,9 @@
package com.google.gcloud.spi;
import com.google.api.services.compute.model.Address;
+import com.google.api.services.compute.model.DeprecationStatus;
import com.google.api.services.compute.model.DiskType;
+import com.google.api.services.compute.model.Image;
import com.google.api.services.compute.model.License;
import com.google.api.services.compute.model.MachineType;
import com.google.api.services.compute.model.Operation;
@@ -103,7 +105,7 @@ public Y y() {
Tuple> listDiskTypes(String zone, Map