github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/plugins/juju-metadata/imagemetadata.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/juju/cmd"
    12  	"github.com/juju/errors"
    13  	"github.com/juju/utils/arch"
    14  	"launchpad.net/gnuflag"
    15  
    16  	"github.com/juju/juju/cmd/envcmd"
    17  	"github.com/juju/juju/environs"
    18  	"github.com/juju/juju/environs/config"
    19  	"github.com/juju/juju/environs/configstore"
    20  	"github.com/juju/juju/environs/filestorage"
    21  	"github.com/juju/juju/environs/imagemetadata"
    22  	"github.com/juju/juju/environs/simplestreams"
    23  	"github.com/juju/juju/environs/storage"
    24  )
    25  
    26  type ImageMetadataCommandBase struct {
    27  	envcmd.EnvCommandBase
    28  }
    29  
    30  func (c *ImageMetadataCommandBase) prepare(context *cmd.Context, store configstore.Storage) (environs.Environ, error) {
    31  	cfg, err := c.Config(store, nil)
    32  	if err != nil {
    33  		return nil, errors.Annotate(err, "could not get config from store")
    34  	}
    35  	// We are preparing an environment to access parameters needed to access
    36  	// image metadata. We don't need, nor want, credential verification.
    37  	// In most cases, credentials will not be available.
    38  	ctx := envcmd.BootstrapContextNoVerify(context)
    39  	return environs.Prepare(cfg, ctx, store)
    40  }
    41  
    42  // ImageMetadataCommand is used to write out simplestreams image metadata information.
    43  type ImageMetadataCommand struct {
    44  	ImageMetadataCommandBase
    45  	Dir            string
    46  	Series         string
    47  	Arch           string
    48  	ImageId        string
    49  	Region         string
    50  	Endpoint       string
    51  	Stream         string
    52  	VirtType       string
    53  	Storage        string
    54  	privateStorage string
    55  }
    56  
    57  var imageMetadataDoc = `
    58  generate-image creates simplestreams image metadata for the specified cloud.
    59  
    60  The cloud specification comes from the current Juju environment, as specified in
    61  the usual way from either ~/.juju/environments.yaml, the -e option, or JUJU_ENV.
    62  
    63  Using command arguments, it is possible to override cloud attributes region, endpoint, and series.
    64  By default, "amd64" is used for the architecture but this may also be changed.
    65  `
    66  
    67  func (c *ImageMetadataCommand) Info() *cmd.Info {
    68  	return &cmd.Info{
    69  		Name:    "generate-image",
    70  		Purpose: "generate simplestreams image metadata",
    71  		Doc:     imageMetadataDoc,
    72  	}
    73  }
    74  
    75  func (c *ImageMetadataCommand) SetFlags(f *gnuflag.FlagSet) {
    76  	f.StringVar(&c.Series, "s", "", "the charm series")
    77  	f.StringVar(&c.Arch, "a", arch.AMD64, "the image achitecture")
    78  	f.StringVar(&c.Dir, "d", "", "the destination directory in which to place the metadata files")
    79  	f.StringVar(&c.ImageId, "i", "", "the image id")
    80  	f.StringVar(&c.Region, "r", "", "the region")
    81  	f.StringVar(&c.Endpoint, "u", "", "the cloud endpoint (for Openstack, this is the Identity Service endpoint)")
    82  	f.StringVar(&c.Stream, "stream", imagemetadata.ReleasedStream, "the image stream")
    83  	f.StringVar(&c.VirtType, "virt-type", "", "the image virtualisation type")
    84  	f.StringVar(&c.Storage, "storage", "", "the type of root storage")
    85  }
    86  
    87  // setParams sets parameters based on the environment configuration
    88  // for those which have not been explicitly specified.
    89  func (c *ImageMetadataCommand) setParams(context *cmd.Context) error {
    90  	c.privateStorage = "<private storage name>"
    91  	var environ environs.Environ
    92  	if store, err := configstore.Default(); err == nil {
    93  		if environ, err = c.prepare(context, store); err == nil {
    94  			logger.Infof("creating image metadata for environment %q", environ.Config().Name())
    95  			// If the user has not specified region and endpoint, try and get it from the environment.
    96  			if c.Region == "" || c.Endpoint == "" {
    97  				var cloudSpec simplestreams.CloudSpec
    98  				if inst, ok := environ.(simplestreams.HasRegion); ok {
    99  					if cloudSpec, err = inst.Region(); err != nil {
   100  						return err
   101  					}
   102  				} else {
   103  					return errors.Errorf("environment %q cannot provide region and endpoint", environ.Config().Name())
   104  				}
   105  				// If only one of region or endpoint is provided, that is a problem.
   106  				if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") {
   107  					return errors.Errorf("cannot generate metadata without a complete cloud configuration")
   108  				}
   109  				if c.Region == "" {
   110  					c.Region = cloudSpec.Region
   111  				}
   112  				if c.Endpoint == "" {
   113  					c.Endpoint = cloudSpec.Endpoint
   114  				}
   115  			}
   116  			cfg := environ.Config()
   117  			if c.Series == "" {
   118  				c.Series = config.PreferredSeries(cfg)
   119  			}
   120  			if v, ok := cfg.AllAttrs()["control-bucket"]; ok {
   121  				c.privateStorage = v.(string)
   122  			}
   123  		} else {
   124  			logger.Warningf("environment could not be opened: %v", err)
   125  		}
   126  	}
   127  	if environ == nil {
   128  		logger.Infof("no environment found, creating image metadata using user supplied data")
   129  	}
   130  	if c.Series == "" {
   131  		c.Series = config.LatestLtsSeries()
   132  	}
   133  	if c.ImageId == "" {
   134  		return errors.Errorf("image id must be specified")
   135  	}
   136  	if c.Region == "" {
   137  		return errors.Errorf("image region must be specified")
   138  	}
   139  	if c.Endpoint == "" {
   140  		return errors.Errorf("cloud endpoint URL must be specified")
   141  	}
   142  	if c.Dir == "" {
   143  		logger.Infof("no destination directory specified, using current directory")
   144  		var err error
   145  		if c.Dir, err = os.Getwd(); err != nil {
   146  			return err
   147  		}
   148  	}
   149  	return nil
   150  }
   151  
   152  var helpDoc = `
   153  Image metadata files have been written to:
   154  %s.
   155  For Juju to use this metadata, the files need to be put into the
   156  image metadata search path. There are 2 options:
   157  
   158  1. Use the --metadata-source parameter when bootstrapping:
   159     juju bootstrap --metadata-source %s
   160  
   161  2. Use image-metadata-url in $JUJU_HOME/environments.yaml
   162  Configure a http server to serve the contents of
   163  %s
   164  and set the value of image-metadata-url accordingly.
   165  `
   166  
   167  func (c *ImageMetadataCommand) Run(context *cmd.Context) error {
   168  	if err := c.setParams(context); err != nil {
   169  		return err
   170  	}
   171  	out := context.Stdout
   172  	im := &imagemetadata.ImageMetadata{
   173  		Id:       c.ImageId,
   174  		Arch:     c.Arch,
   175  		Stream:   c.Stream,
   176  		VirtType: c.VirtType,
   177  		Storage:  c.Storage,
   178  	}
   179  	cloudSpec := simplestreams.CloudSpec{
   180  		Region:   c.Region,
   181  		Endpoint: c.Endpoint,
   182  	}
   183  	targetStorage, err := filestorage.NewFileStorageWriter(c.Dir)
   184  	if err != nil {
   185  		return err
   186  	}
   187  	err = imagemetadata.MergeAndWriteMetadata(c.Series, []*imagemetadata.ImageMetadata{im}, &cloudSpec, targetStorage)
   188  	if err != nil {
   189  		return fmt.Errorf("image metadata files could not be created: %v", err)
   190  	}
   191  	dir := context.AbsPath(c.Dir)
   192  	dest := filepath.Join(dir, storage.BaseImagesPath, "streams", "v1")
   193  	fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, dir, dir))
   194  	return nil
   195  }