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