github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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  	"github.com/juju/utils/series"
    15  	"launchpad.net/gnuflag"
    16  
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  	"github.com/juju/juju/environs"
    19  	"github.com/juju/juju/environs/config"
    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  	modelcmd.ModelCommandBase
    28  }
    29  
    30  func (c *imageMetadataCommandBase) prepare(context *cmd.Context) (environs.Environ, error) {
    31  	// NOTE(axw) this is a work-around for the TODO below. This
    32  	// means that the command will only work if you've bootstrapped
    33  	// the specified environment.
    34  	cfg, err := modelcmd.NewGetBootstrapConfigFunc(c.ClientStore())(c.ControllerName())
    35  	if err != nil {
    36  		return nil, errors.Trace(err)
    37  	}
    38  	// TODO(axw) we'll need to revise the metadata commands to work
    39  	// without preparing an environment. They should take the same
    40  	// format as bootstrap, i.e. cloud/region, and we'll use that to
    41  	// identify region and endpoint info that we need. Not sure what
    42  	// we'll do about simplestreams.MetadataValidator yet. Probably
    43  	// move it to the EnvironProvider interface.
    44  	return environs.New(cfg)
    45  }
    46  
    47  func newImageMetadataCommand() cmd.Command {
    48  	return modelcmd.Wrap(&imageMetadataCommand{})
    49  }
    50  
    51  // imageMetadataCommand is used to write out simplestreams image metadata information.
    52  type imageMetadataCommand struct {
    53  	imageMetadataCommandBase
    54  	Dir            string
    55  	Series         string
    56  	Arch           string
    57  	ImageId        string
    58  	Region         string
    59  	Endpoint       string
    60  	Stream         string
    61  	VirtType       string
    62  	Storage        string
    63  	privateStorage string
    64  }
    65  
    66  var imageMetadataDoc = `
    67  generate-image creates simplestreams image metadata for the specified cloud.
    68  
    69  The cloud specification comes from the current Juju model, as specified in
    70  the usual way from either the -m option, or JUJU_MODEL.
    71  
    72  Using command arguments, it is possible to override cloud attributes region, endpoint, and series.
    73  By default, "amd64" is used for the architecture but this may also be changed.
    74  `
    75  
    76  func (c *imageMetadataCommand) Info() *cmd.Info {
    77  	return &cmd.Info{
    78  		Name:    "generate-image",
    79  		Purpose: "generate simplestreams image metadata",
    80  		Doc:     imageMetadataDoc,
    81  	}
    82  }
    83  
    84  func (c *imageMetadataCommand) SetFlags(f *gnuflag.FlagSet) {
    85  	f.StringVar(&c.Series, "s", "", "the charm series")
    86  	f.StringVar(&c.Arch, "a", arch.AMD64, "the image achitecture")
    87  	f.StringVar(&c.Dir, "d", "", "the destination directory in which to place the metadata files")
    88  	f.StringVar(&c.ImageId, "i", "", "the image id")
    89  	f.StringVar(&c.Region, "r", "", "the region")
    90  	f.StringVar(&c.Endpoint, "u", "", "the cloud endpoint (for Openstack, this is the Identity Service endpoint)")
    91  	f.StringVar(&c.Stream, "stream", imagemetadata.ReleasedStream, "the image stream")
    92  	f.StringVar(&c.VirtType, "virt-type", "", "the image virtualisation type")
    93  	f.StringVar(&c.Storage, "storage", "", "the type of root storage")
    94  }
    95  
    96  // setParams sets parameters based on the environment configuration
    97  // for those which have not been explicitly specified.
    98  func (c *imageMetadataCommand) setParams(context *cmd.Context) error {
    99  	c.privateStorage = "<private storage name>"
   100  	var environ environs.Environ
   101  	if environ, err := c.prepare(context); err == nil {
   102  		logger.Infof("creating image metadata for model %q", environ.Config().Name())
   103  		// If the user has not specified region and endpoint, try and get it from the environment.
   104  		if c.Region == "" || c.Endpoint == "" {
   105  			var cloudSpec simplestreams.CloudSpec
   106  			if inst, ok := environ.(simplestreams.HasRegion); ok {
   107  				if cloudSpec, err = inst.Region(); err != nil {
   108  					return err
   109  				}
   110  			} else {
   111  				return errors.Errorf("model %q cannot provide region and endpoint", environ.Config().Name())
   112  			}
   113  			// If only one of region or endpoint is provided, that is a problem.
   114  			if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") {
   115  				return errors.Errorf("cannot generate metadata without a complete cloud configuration")
   116  			}
   117  			if c.Region == "" {
   118  				c.Region = cloudSpec.Region
   119  			}
   120  			if c.Endpoint == "" {
   121  				c.Endpoint = cloudSpec.Endpoint
   122  			}
   123  		}
   124  		cfg := environ.Config()
   125  		if c.Series == "" {
   126  			c.Series = config.PreferredSeries(cfg)
   127  		}
   128  	} else {
   129  		logger.Warningf("model could not be opened: %v", err)
   130  	}
   131  	if environ == nil {
   132  		logger.Infof("no model found, creating image metadata using user supplied data")
   133  	}
   134  	if c.Series == "" {
   135  		c.Series = series.LatestLts()
   136  	}
   137  	if c.ImageId == "" {
   138  		return errors.Errorf("image id must be specified")
   139  	}
   140  	if c.Region == "" {
   141  		return errors.Errorf("image region must be specified")
   142  	}
   143  	if c.Endpoint == "" {
   144  		return errors.Errorf("cloud endpoint URL must be specified")
   145  	}
   146  	if c.Dir == "" {
   147  		logger.Infof("no destination directory specified, using current directory")
   148  		var err error
   149  		if c.Dir, err = os.Getwd(); err != nil {
   150  			return err
   151  		}
   152  	}
   153  	return nil
   154  }
   155  
   156  var helpDoc = `
   157  Image metadata files have been written to:
   158  %s.
   159  For Juju to use this metadata, the files need to be put into the
   160  image metadata search path. There are 2 options:
   161  
   162  1. Use the --metadata-source parameter when bootstrapping:
   163     juju bootstrap --metadata-source %s
   164  
   165  2. Use image-metadata-url in $JUJU_DATA/environments.yaml
   166  (if $JUJU_DATA is not set it will try $XDG_DATA_HOME/juju and
   167  if not set either default to ~/.local/share/juju)
   168  Configure a http server to serve the contents of
   169  %s
   170  and set the value of image-metadata-url accordingly.
   171  `
   172  
   173  func (c *imageMetadataCommand) Run(context *cmd.Context) error {
   174  	if err := c.setParams(context); err != nil {
   175  		return err
   176  	}
   177  	out := context.Stdout
   178  	im := &imagemetadata.ImageMetadata{
   179  		Id:       c.ImageId,
   180  		Arch:     c.Arch,
   181  		Stream:   c.Stream,
   182  		VirtType: c.VirtType,
   183  		Storage:  c.Storage,
   184  	}
   185  	cloudSpec := simplestreams.CloudSpec{
   186  		Region:   c.Region,
   187  		Endpoint: c.Endpoint,
   188  	}
   189  	targetStorage, err := filestorage.NewFileStorageWriter(c.Dir)
   190  	if err != nil {
   191  		return err
   192  	}
   193  	err = imagemetadata.MergeAndWriteMetadata(c.Series, []*imagemetadata.ImageMetadata{im}, &cloudSpec, targetStorage)
   194  	if err != nil {
   195  		return fmt.Errorf("image metadata files could not be created: %v", err)
   196  	}
   197  	dir := context.AbsPath(c.Dir)
   198  	dest := filepath.Join(dir, storage.BaseImagesPath, "streams", "v1")
   199  	fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, dir, dir))
   200  	return nil
   201  }