github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"launchpad.net/gnuflag"
    14  
    15  	"github.com/juju/juju/cmd/envcmd"
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/environs/configstore"
    19  	"github.com/juju/juju/environs/filestorage"
    20  	"github.com/juju/juju/environs/imagemetadata"
    21  	"github.com/juju/juju/environs/simplestreams"
    22  	"github.com/juju/juju/environs/storage"
    23  	"github.com/juju/juju/juju/arch"
    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)
    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  	privateStorage string
    52  }
    53  
    54  var imageMetadataDoc = `
    55  generate-image creates simplestreams image metadata for the specified cloud.
    56  
    57  The cloud specification comes from the current Juju environment, as specified in
    58  the usual way from either ~/.juju/environments.yaml, the -e option, or JUJU_ENV.
    59  
    60  Using command arguments, it is possible to override cloud attributes region, endpoint, and series.
    61  By default, "amd64" is used for the architecture but this may also be changed.
    62  `
    63  
    64  func (c *ImageMetadataCommand) Info() *cmd.Info {
    65  	return &cmd.Info{
    66  		Name:    "generate-image",
    67  		Purpose: "generate simplestreams image metadata",
    68  		Doc:     imageMetadataDoc,
    69  	}
    70  }
    71  
    72  func (c *ImageMetadataCommand) SetFlags(f *gnuflag.FlagSet) {
    73  	f.StringVar(&c.Series, "s", "", "the charm series")
    74  	f.StringVar(&c.Arch, "a", arch.AMD64, "the image achitecture")
    75  	f.StringVar(&c.Dir, "d", "", "the destination directory in which to place the metadata files")
    76  	f.StringVar(&c.ImageId, "i", "", "the image id")
    77  	f.StringVar(&c.Region, "r", "", "the region")
    78  	f.StringVar(&c.Endpoint, "u", "", "the cloud endpoint (for Openstack, this is the Identity Service endpoint)")
    79  }
    80  
    81  // setParams sets parameters based on the environment configuration
    82  // for those which have not been explicitly specified.
    83  func (c *ImageMetadataCommand) setParams(context *cmd.Context) error {
    84  	c.privateStorage = "<private storage name>"
    85  	var environ environs.Environ
    86  	if store, err := configstore.Default(); err == nil {
    87  		if environ, err = c.prepare(context, store); err == nil {
    88  			logger.Infof("creating image metadata for environment %q", environ.Config().Name())
    89  			// If the user has not specified region and endpoint, try and get it from the environment.
    90  			if c.Region == "" || c.Endpoint == "" {
    91  				var cloudSpec simplestreams.CloudSpec
    92  				if inst, ok := environ.(simplestreams.HasRegion); ok {
    93  					if cloudSpec, err = inst.Region(); err != nil {
    94  						return err
    95  					}
    96  				} else {
    97  					return errors.Errorf("environment %q cannot provide region and endpoint", environ.Config().Name())
    98  				}
    99  				// If only one of region or endpoint is provided, that is a problem.
   100  				if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") {
   101  					return errors.Errorf("cannot generate metadata without a complete cloud configuration")
   102  				}
   103  				if c.Region == "" {
   104  					c.Region = cloudSpec.Region
   105  				}
   106  				if c.Endpoint == "" {
   107  					c.Endpoint = cloudSpec.Endpoint
   108  				}
   109  			}
   110  			cfg := environ.Config()
   111  			if c.Series == "" {
   112  				c.Series = config.PreferredSeries(cfg)
   113  			}
   114  			if v, ok := cfg.AllAttrs()["control-bucket"]; ok {
   115  				c.privateStorage = v.(string)
   116  			}
   117  		} else {
   118  			logger.Warningf("environment could not be opened: %v", err)
   119  		}
   120  	}
   121  	if environ == nil {
   122  		logger.Infof("no environment found, creating image metadata using user supplied data")
   123  	}
   124  	if c.Series == "" {
   125  		c.Series = config.LatestLtsSeries()
   126  	}
   127  	if c.ImageId == "" {
   128  		return errors.Errorf("image id must be specified")
   129  	}
   130  	if c.Region == "" {
   131  		return errors.Errorf("image region must be specified")
   132  	}
   133  	if c.Endpoint == "" {
   134  		return errors.Errorf("cloud endpoint URL must be specified")
   135  	}
   136  	if c.Dir == "" {
   137  		logger.Infof("no destination directory specified, using current directory")
   138  		var err error
   139  		if c.Dir, err = os.Getwd(); err != nil {
   140  			return err
   141  		}
   142  	}
   143  	return nil
   144  }
   145  
   146  var helpDoc = `
   147  image metadata files have been written to:
   148  %s.
   149  For Juju to use this metadata, the files need to be put into the
   150  image metadata search path. There are 2 options:
   151  
   152  1. Use the --metadata-source parameter when bootstrapping:
   153     juju bootstrap --metadata-source %s
   154  
   155  2. Use image-metadata-url in $JUJU_HOME/environments.yaml
   156  Configure a http server to serve the contents of
   157  %s
   158  and set the value of image-metadata-url accordingly.
   159  
   160  "
   161  
   162  `
   163  
   164  func (c *ImageMetadataCommand) Run(context *cmd.Context) error {
   165  	if err := c.setParams(context); err != nil {
   166  		return err
   167  	}
   168  	out := context.Stdout
   169  	im := &imagemetadata.ImageMetadata{
   170  		Id:   c.ImageId,
   171  		Arch: c.Arch,
   172  	}
   173  	cloudSpec := simplestreams.CloudSpec{
   174  		Region:   c.Region,
   175  		Endpoint: c.Endpoint,
   176  	}
   177  	targetStorage, err := filestorage.NewFileStorageWriter(c.Dir)
   178  	if err != nil {
   179  		return err
   180  	}
   181  	err = imagemetadata.MergeAndWriteMetadata(c.Series, []*imagemetadata.ImageMetadata{im}, &cloudSpec, targetStorage)
   182  	if err != nil {
   183  		return fmt.Errorf("image metadata files could not be created: %v", err)
   184  	}
   185  	dir := context.AbsPath(c.Dir)
   186  	dest := filepath.Join(dir, storage.BaseImagesPath, "streams", "v1")
   187  	fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, dir, dir))
   188  	return nil
   189  }