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