/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.brooklyn.cli;
import static com.google.common.base.Preconditions.checkNotNull;
import io.airlift.command.Command;
import io.airlift.command.Option;
import io.airlift.command.ParseException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.blobstore.domain.StorageMetadata;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.domain.ComputeMetadata;
import org.jclouds.compute.domain.Hardware;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.options.TemplateOptions;
import org.apache.brooklyn.api.location.Location;
import org.apache.brooklyn.api.location.LocationDefinition;
import org.apache.brooklyn.core.location.LocationConfigKeys;
import org.apache.brooklyn.core.location.cloud.CloudLocationConfig;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.location.jclouds.JcloudsLocation;
import org.apache.brooklyn.location.jclouds.JcloudsUtil;
import org.apache.brooklyn.util.exceptions.FatalConfigurationRuntimeException;
import org.apache.brooklyn.util.stream.Streams;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.collect.Lists;
/**
* Convenience for listing Cloud Compute and BlobStore details.
* <p>
* For fuller functionality, consider instead the jclouds CLI or Ruby Fog CLI.
* <p>
* The advantage of this utility is that it piggie-backs off the {@code brooklyn.property} credentials,
* so requires less additional credential configuration. It also gives brooklyn-specific information,
* such as which image will be used by default in a given cloud.
*/
public class CloudExplorer {
public static abstract class JcloudsCommand extends AbstractMain.BrooklynCommandCollectingArgs {
@Option(name = { "--all-locations" }, title = "all locations",
description = "All locations (i.e. all locations in brooklyn.properties for which there are credentials)")
public boolean allLocations;
@Option(name = { "-l", "--location" }, title = "location spec",
description = "A location spec (e.g. referring to a named location in brooklyn.properties file)")
public String location;
@Option(name = { "-y", "--yes" }, title = "auto-confirm",
description = "Automatically answer yes to any questions")
public boolean autoconfirm = false;
protected abstract void doCall(JcloudsLocation loc, String indent) throws Exception;
@Override
public Void call() throws Exception {
LocalManagementContext mgmt = new LocalManagementContext();
List<JcloudsLocation> locs = Lists.newArrayList();
try {
if (location != null && allLocations) {
throw new FatalConfigurationRuntimeException("Must not specify --location and --all-locations");
} else if (location != null) {
JcloudsLocation loc = (JcloudsLocation) mgmt.getLocationRegistry().resolve(location);
locs.add(loc);
} else if (allLocations) {
// Find all named locations that point at different target clouds
Map<String, LocationDefinition> definedLocations = mgmt.getLocationRegistry().getDefinedLocations();
for (LocationDefinition locationDef : definedLocations.values()) {
Location loc = mgmt.getLocationRegistry().resolve(locationDef);
if (loc instanceof JcloudsLocation) {
boolean found = false;
for (JcloudsLocation existing : locs) {
if (equalTargets(existing, (JcloudsLocation) loc)) {
found = true;
break;
}
}
if (!found) {
locs.add((JcloudsLocation) loc);
}
}
}
} else {
throw new FatalConfigurationRuntimeException("Must specify one of --location or --all-locations");
}
for (JcloudsLocation loc : locs) {
stdout.println("Location {");
stdout.println("\tprovider: "+loc.getProvider());
stdout.println("\tdisplayName: "+loc.getDisplayName());
stdout.println("\tidentity: "+loc.getIdentity());
if (loc.getEndpoint() != null) stdout.println("\tendpoint: "+loc.getEndpoint());
if (loc.getRegion() != null) stdout.println("\tregion: "+loc.getRegion());
try {
doCall(loc, "\t");
} finally {
stdout.println("}");
}
}
} finally {
mgmt.terminate();
}
return null;
}
@Override
public ToStringHelper string() {
return super.string()
.add("location", location);
}
protected boolean equalTargets(JcloudsLocation loc1, JcloudsLocation loc2) {
return Objects.equal(loc1.getProvider(), loc2.getProvider())
&& Objects.equal(loc1.getIdentity(), loc2.getIdentity())
&& Objects.equal(loc1.getEndpoint(), loc2.getEndpoint())
&& Objects.equal(loc1.getRegion(), loc2.getRegion());
}
protected boolean confirm(String msg, String indent) throws Exception {
if (autoconfirm) {
stdout.println(indent+"Auto-confirmed: "+msg);
return true;
} else {
stdout.println(indent+"Enter y/n. Are you sure you want to "+msg);
int in = stdin.read();
boolean confirmed = (Character.toLowerCase(in) == 'y');
if (confirmed) {
stdout.println(indent+"Confirmed; will "+msg);
} else {
stdout.println(indent+"Declined; will not "+msg);
}
return confirmed;
}
}
}
public static abstract class ComputeCommand extends JcloudsCommand {
protected abstract void doCall(ComputeService computeService, String indent) throws Exception;
@Override
protected void doCall(JcloudsLocation loc, String indent) throws Exception {
ComputeService computeService = loc.getComputeService();
doCall(computeService, indent);
}
}
@Command(name = "list-instances", description = "")
public static class ComputeListInstancesCommand extends ComputeCommand {
@Override
protected void doCall(ComputeService computeService, String indent) throws Exception {
failIfArguments();
Set<? extends ComputeMetadata> instances = computeService.listNodes();
stdout.println(indent+"Instances {");
for (ComputeMetadata instance : instances) {
stdout.println(indent+"\t"+instance);
}
stdout.println(indent+"}");
}
}
@Command(name = "list-images", description = "")
public static class ComputeListImagesCommand extends ComputeCommand {
@Override
protected void doCall(ComputeService computeService, String indent) throws Exception {
failIfArguments();
Set<? extends Image> images = computeService.listImages();
stdout.println(indent+"Images {");
for (Image image : images) {
stdout.println(indent+"\t"+image);
}
stdout.println(indent+"}");
}
}
@Command(name = "list-hardware-profiles", description = "")
public static class ComputeListHardwareProfilesCommand extends ComputeCommand {
@Override
protected void doCall(ComputeService computeService, String indent) throws Exception {
failIfArguments();
Set<? extends Hardware> hardware = computeService.listHardwareProfiles();
stdout.println(indent+"Hardware Profiles {");
for (Hardware image : hardware) {
stdout.println(indent+"\t"+image);
}
stdout.println(indent+"}");
}
}
@Command(name = "get-image", description = "")
public static class ComputeGetImageCommand extends ComputeCommand {
@Override
protected void doCall(ComputeService computeService, String indent) throws Exception {
if (arguments.isEmpty()) {
throw new ParseException("Requires at least one image-id arguments");
}
for (String imageId : arguments) {
Image image = computeService.getImage(imageId);
stdout.println(indent+"Image "+imageId+" {");
stdout.println(indent+"\t"+image);
stdout.println(indent+"}");
}
}
@Override
public ToStringHelper string() {
return super.string()
.add("imageIds", arguments);
}
}
@Command(name = "default-template", description = "")
public static class ComputeDefaultTemplateCommand extends JcloudsCommand {
@Override
protected void doCall(JcloudsLocation loc, String indent) throws Exception {
failIfArguments();
ComputeService computeService = loc.getComputeService();
Template template = loc.buildTemplate(computeService, loc.config().getBag());
Image image = template.getImage();
Hardware hardware = template.getHardware();
org.jclouds.domain.Location location = template.getLocation();
TemplateOptions options = template.getOptions();
stdout.println(indent+"Default template {");
stdout.println(indent+"\tImage: "+image);
stdout.println(indent+"\tHardware: "+hardware);
stdout.println(indent+"\tLocation: "+location);
stdout.println(indent+"\tOptions: "+options);
stdout.println(indent+"}");
}
}
@Command(name = "terminate-instances", description = "")
public static class ComputeTerminateInstancesCommand extends ComputeCommand {
@Override
protected void doCall(ComputeService computeService, String indent) throws Exception {
if (arguments.isEmpty()) {
throw new ParseException("Requires at least one instance-id arguments");
}
for (String instanceId : arguments) {
NodeMetadata instance = computeService.getNodeMetadata(instanceId);
if (instance == null) {
stderr.println(indent+"Cannot terminate instance; could not find "+instanceId);
} else {
boolean confirmed = confirm(indent, "terminate "+instanceId+" ("+instance+")");
if (confirmed) {
computeService.destroyNode(instanceId);
}
}
}
}
@Override
public ToStringHelper string() {
return super.string()
.add("instanceIds", arguments);
}
}
public static abstract class BlobstoreCommand extends JcloudsCommand {
protected abstract void doCall(BlobStore blobstore, String indent) throws Exception;
@Override
protected void doCall(JcloudsLocation loc, String indent) throws Exception {
String identity = checkNotNull(loc.getConfig(LocationConfigKeys.ACCESS_IDENTITY), "identity must not be null");
String credential = checkNotNull(loc.getConfig(LocationConfigKeys.ACCESS_CREDENTIAL), "credential must not be null");
String provider = checkNotNull(loc.getConfig(LocationConfigKeys.CLOUD_PROVIDER), "provider must not be null");
String endpoint = loc.getConfig(CloudLocationConfig.CLOUD_ENDPOINT);
BlobStoreContext context = JcloudsUtil.newBlobstoreContext(provider, endpoint, identity, credential);
try {
BlobStore blobStore = context.getBlobStore();
doCall(blobStore, indent);
} finally {
context.close();
}
}
}
@Command(name = "list-containers", description = "")
public static class BlobstoreListContainersCommand extends BlobstoreCommand {
@Override
protected void doCall(BlobStore blobstore, String indent) throws Exception {
failIfArguments();
Set<? extends StorageMetadata> containers = blobstore.list();
stdout.println(indent+"Containers {");
for (StorageMetadata container : containers) {
stdout.println(indent+"\t"+container);
}
stdout.println(indent+"}");
}
}
@Command(name = "list-container", description = "")
public static class BlobstoreListContainerCommand extends BlobstoreCommand {
@Override
protected void doCall(BlobStore blobStore, String indent) throws Exception {
if (arguments.isEmpty()) {
throw new ParseException("Requires at least one container-name arguments");
}
for (String containerName : arguments) {
Set<? extends StorageMetadata> contents = blobStore.list(containerName);
stdout.println(indent+"Container "+containerName+" {");
for (StorageMetadata content : contents) {
stdout.println(indent+"\t"+content);
}
stdout.println(indent+"}");
}
}
@Override
public ToStringHelper string() {
return super.string()
.add("containers", arguments);
}
}
@Command(name = "blob", description = "")
public static class BlobstoreGetBlobCommand extends BlobstoreCommand {
@Option(name = { "--container" }, title = "list contents of a given container",
description = "")
public String container;
@Option(name = { "--blob" }, title = "retrieves the blog in the given container",
description = "")
public String blob;
@Override
protected void doCall(BlobStore blobStore, String indent) throws Exception {
failIfArguments();
Blob content = blobStore.getBlob(container, blob);
stdout.println(indent+"Blob "+container+" : " +blob +" {");
stdout.println(indent+"\tHeaders {");
for (Map.Entry<String, String> entry : content.getAllHeaders().entries()) {
stdout.println(indent+"\t\t"+entry.getKey() + " = " + entry.getValue());
}
stdout.println(indent+"\t}");
stdout.println(indent+"\tmetadata : "+content.getMetadata());
stdout.println(indent+"\tpayload : "+Streams.readFullyString(content.getPayload().openStream()));
stdout.println(indent+"}");
}
@Override
public ToStringHelper string() {
return super.string()
.add("container", container)
.add("blob", blob);
}
}
}