diff --git a/compute/cloud-client/src/main/java/compute/windows/osimage/CreateWindowsOsImage.java b/compute/cloud-client/src/main/java/compute/windows/osimage/CreateWindowsOsImage.java new file mode 100644 index 00000000000..a73d56142ad --- /dev/null +++ b/compute/cloud-client/src/main/java/compute/windows/osimage/CreateWindowsOsImage.java @@ -0,0 +1,134 @@ +// Copyright 2022 Google LLC +// +// 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 compute.windows.osimage; + +// [START compute_windows_image_create] + +import com.google.cloud.compute.v1.Disk; +import com.google.cloud.compute.v1.DisksClient; +import com.google.cloud.compute.v1.Image; +import com.google.cloud.compute.v1.ImagesClient; +import com.google.cloud.compute.v1.InsertImageRequest; +import com.google.cloud.compute.v1.Instance; +import com.google.cloud.compute.v1.InstancesClient; +import com.google.cloud.compute.v1.Operation; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +public class CreateWindowsOsImage { + + public static void main(String[] args) + throws IOException, ExecutionException, InterruptedException { + // TODO(developer): Replace these variables before running the sample. + + // Project ID or project number of the Cloud project you use. + String project = "your-project-id"; + // Zone of the disk you copy from. + String zone = "europe-central2-b"; + // Name of the source disk you copy from. + String sourceDiskName = "source-disk-name"; + // Name of the image you want to create. + String imageName = "your-image-name"; + // Storage location for the image. If the value is undefined, + // function will store the image in the multi-region closest to your image's source location. + String storageLocation = "eu"; + // Create the image even if the source disk is attached to a running instance. + boolean forceCreate = false; + + createWindowsOsImage(project, zone, sourceDiskName, imageName, storageLocation, forceCreate); + } + + // Creates a new Windows image from the specified source disk. + public static void createWindowsOsImage(String project, String zone, String sourceDiskName, + String imageName, String storageLocation, boolean forceCreate) + throws IOException, ExecutionException, InterruptedException { + + try (ImagesClient imagesClient = ImagesClient.create(); + InstancesClient instancesClient = InstancesClient.create(); + DisksClient disksClient = DisksClient.create()) { + + Disk disk = disksClient.get(project, zone, sourceDiskName); + + // Getting instances where source disk is attached. + for (String fullInstanceName : disk.getUsersList()) { + Map instanceInfo = parseInstanceName(fullInstanceName); + Instance instance = instancesClient.get(instanceInfo.get("instanceProjectId"), + instanceInfo.get("instanceZone"), instanceInfo.get("instanceName")); + + // Сhecking whether the instances is stopped. + if (!Arrays.asList("TERMINATED", "STOPPED").contains(instance.getStatus()) + && !forceCreate) { + throw new IllegalStateException( + String.format( + "Instance %s should be stopped. Please stop the instance using GCESysprep command or set forceCreate parameter to true (not recommended). More information here: https://cloud.google.com/compute/docs/instances/windows/creating-windows-os-image#api.", + instanceInfo.get("instanceName"))); + } + } + + if (forceCreate) { + System.out.println( + "Warning: forceCreate option compromise the integrity of your image. " + + "Stop the instance before you create the image if possible."); + } + + // Create Image. + Image image = Image.newBuilder() + .setName(imageName) + .setSourceDisk(String.format("/zones/%s/disks/%s", zone, sourceDiskName)) + .addStorageLocations(storageLocation.isEmpty() ? "" : storageLocation) + .build(); + + InsertImageRequest insertImageRequest = InsertImageRequest.newBuilder() + .setProject(project) + .setForceCreate(forceCreate) + .setImageResource(image) + .build(); + + Operation response = imagesClient.insertAsync(insertImageRequest).get(); + + if (response.hasError()) { + System.out.println("Windows OS Image creation failed ! ! " + response); + return; + } + + System.out.println("Image created."); + } + } + + + public static Map parseInstanceName(String name) { + String[] parsedName = name.split("/"); + int splitLength = parsedName.length; + + if (splitLength < 5) { + throw new IllegalArgumentException( + "Provide correct instance name in the following format: " + + "https://www.googleapis.com/compute/v1/projects/PROJECT/zones/ZONE/instances/INSTANCE_NAME"); + } + + return new HashMap<>() { + { + put("instanceName", parsedName[splitLength - 1]); + put("instanceZone", parsedName[splitLength - 3]); + put("instanceProjectId", parsedName[splitLength - 5]); + } + }; + } + +} +// [END compute_windows_image_create] \ No newline at end of file diff --git a/compute/cloud-client/src/test/java/compute/windows/osimage/WindowsOsImageIT.java b/compute/cloud-client/src/test/java/compute/windows/osimage/WindowsOsImageIT.java new file mode 100644 index 00000000000..82fb84ceab5 --- /dev/null +++ b/compute/cloud-client/src/test/java/compute/windows/osimage/WindowsOsImageIT.java @@ -0,0 +1,170 @@ +// Copyright 2022 Google LLC +// +// 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 compute.windows.osimage; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.cloud.compute.v1.AttachedDisk; +import com.google.cloud.compute.v1.AttachedDiskInitializeParams; +import com.google.cloud.compute.v1.ImagesClient; +import com.google.cloud.compute.v1.InsertInstanceRequest; +import com.google.cloud.compute.v1.Instance; +import com.google.cloud.compute.v1.InstancesClient; +import com.google.cloud.compute.v1.NetworkInterface; +import com.google.cloud.compute.v1.Operation; +import compute.DeleteInstance; +import compute.Util; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class WindowsOsImageIT { + + private static final String PROJECT_ID = System.getenv("GOOGLE_CLOUD_PROJECT"); + private static final String ZONE = "us-central1-a"; + private static String INSTANCE_NAME; + private static String DISK_NAME; + private static String IMAGE_NAME; + @Rule + public ExpectedException expectedException = ExpectedException.none(); + private ByteArrayOutputStream stdOut; + + // Check if the required environment variables are set. + public static void requireEnvVar(String envVarName) { + assertWithMessage(String.format("Missing environment variable '%s' ", envVarName)) + .that(System.getenv(envVarName)).isNotEmpty(); + } + + @BeforeClass + public static void setup() throws IOException, ExecutionException, InterruptedException { + final PrintStream out = System.out; + ByteArrayOutputStream stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + + requireEnvVar("GOOGLE_APPLICATION_CREDENTIALS"); + requireEnvVar("GOOGLE_CLOUD_PROJECT"); + + // Cleanup existing test instances. + Util.cleanUpExistingInstances("windowsimage-test-instance-", PROJECT_ID, ZONE); + + String randomUUID = UUID.randomUUID().toString().split("-")[0]; + INSTANCE_NAME = "windowsimage-test-instance-" + randomUUID; + DISK_NAME = "windowsimage-test-disk-" + randomUUID; + IMAGE_NAME = "windowsimage-test-image-" + randomUUID; + + // Create Instance with Windows source image. + try (InstancesClient instancesClient = InstancesClient.create()) { + AttachedDisk attachedDisk = AttachedDisk.newBuilder() + .setDeviceName(DISK_NAME) + .setAutoDelete(true) + .setBoot(true) + .setType(AttachedDisk.Type.PERSISTENT.name()) + .setInitializeParams(AttachedDiskInitializeParams.newBuilder() + .setDiskName(DISK_NAME) + .setDiskSizeGb(64) + .setSourceImage( + "projects/windows-cloud/global/images/windows-server-2012-r2-dc-core-v20220314") + .build()) + .build(); + + Instance instance = Instance.newBuilder() + .setName(INSTANCE_NAME) + .setMachineType(String.format("zones/%s/machineTypes/n1-standard-1", ZONE)) + .addNetworkInterfaces(NetworkInterface.newBuilder() + .setName("global/networks/default") + .build()) + .addDisks(attachedDisk) + .build(); + + InsertInstanceRequest request = InsertInstanceRequest.newBuilder() + .setProject(PROJECT_ID) + .setZone(ZONE) + .setInstanceResource(instance) + .build(); + + Operation response = instancesClient.insertAsync(request).get(); + Assert.assertFalse(response.hasError()); + } + + stdOut.close(); + System.setOut(out); + } + + @AfterClass + public static void cleanUp() throws IOException, ExecutionException, InterruptedException { + final PrintStream out = System.out; + ByteArrayOutputStream stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + + // Delete image. + ImagesClient imagesClient = ImagesClient.create(); + Operation operation = imagesClient.deleteAsync(PROJECT_ID, IMAGE_NAME).get(); + if (operation.hasError()) { + System.out.println("Image not deleted."); + } + + // Delete instance. + DeleteInstance.deleteInstance(PROJECT_ID, ZONE, INSTANCE_NAME); + + stdOut.close(); + System.setOut(out); + } + + @Before + public void beforeEach() { + stdOut = new ByteArrayOutputStream(); + System.setOut(new PrintStream(stdOut)); + } + + @After + public void afterEach() { + stdOut = null; + System.setOut(null); + } + + + @Test + public void testCreateWindowsImage_failDueToRunningInstance() + throws IOException, ExecutionException, InterruptedException { + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage(String.format("Instance %s should be stopped.", INSTANCE_NAME)); + CreateWindowsOsImage.createWindowsOsImage( + PROJECT_ID, ZONE, DISK_NAME, IMAGE_NAME, "eu", false); + } + + + @Test + public void testCreateWindowsImage_pass() + throws IOException, ExecutionException, InterruptedException { + CreateWindowsOsImage.createWindowsOsImage( + PROJECT_ID, ZONE, DISK_NAME, IMAGE_NAME, "eu", true); + assertThat(stdOut.toString()).contains("Image created."); + } + +}