github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  
    16  	jujucmd "github.com/juju/juju/cmd"
    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  	"github.com/juju/juju/juju/version"
    25  	"github.com/juju/juju/jujuclient"
    26  )
    27  
    28  func prepare(context *cmd.Context, controllerName string, store jujuclient.ClientStore) (environs.Environ, error) {
    29  	// NOTE(axw) this is a work-around for the TODO below. This
    30  	// means that the command will only work if you've bootstrapped
    31  	// the specified environment.
    32  	bootstrapConfig, params, err := modelcmd.NewGetBootstrapConfigParamsFunc(
    33  		context, store, environs.GlobalProviderRegistry(),
    34  	)(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.WrapController(&imageMetadataCommand{})
    60  }
    61  
    62  // imageMetadataCommand is used to write out simplestreams image metadata information.
    63  type imageMetadataCommand struct {
    64  	modelcmd.ControllerCommandBase
    65  
    66  	Dir            string
    67  	Series         string
    68  	Arch           string
    69  	ImageId        string
    70  	Region         string
    71  	Endpoint       string
    72  	Stream         string
    73  	VirtType       string
    74  	Storage        string
    75  	privateStorage string
    76  }
    77  
    78  var imageMetadataDoc = `
    79  generate-image creates simplestreams image metadata for the specified cloud.
    80  
    81  The cloud specification comes from the current Juju model, as specified in
    82  the usual way from either the -m option, or JUJU_MODEL.
    83  
    84  Using command arguments, it is possible to override cloud attributes region, endpoint, and series.
    85  By default, "amd64" is used for the architecture but this may also be changed.
    86  `
    87  
    88  func (c *imageMetadataCommand) Info() *cmd.Info {
    89  	return jujucmd.Info(&cmd.Info{
    90  		Name:    "generate-image",
    91  		Purpose: "generate simplestreams image metadata",
    92  		Doc:     imageMetadataDoc,
    93  	})
    94  }
    95  
    96  func (c *imageMetadataCommand) SetFlags(f *gnuflag.FlagSet) {
    97  	f.StringVar(&c.Series, "s", "", "the charm series")
    98  	f.StringVar(&c.Arch, "a", arch.AMD64, "the image achitecture")
    99  	f.StringVar(&c.Dir, "d", "", "the destination directory in which to place the metadata files")
   100  	f.StringVar(&c.ImageId, "i", "", "the image id")
   101  	f.StringVar(&c.Region, "r", "", "the region")
   102  	f.StringVar(&c.Endpoint, "u", "", "the cloud endpoint (for Openstack, this is the Identity Service endpoint)")
   103  	f.StringVar(&c.Stream, "stream", imagemetadata.ReleasedStream, "the image stream")
   104  	f.StringVar(&c.VirtType, "virt-type", "", "the image virtualisation type")
   105  	f.StringVar(&c.Storage, "storage", "", "the type of root storage")
   106  }
   107  
   108  // setParams sets parameters based on the environment configuration
   109  // for those which have not been explicitly specified.
   110  func (c *imageMetadataCommand) setParams(context *cmd.Context) error {
   111  	c.privateStorage = "<private storage name>"
   112  
   113  	controllerName, err := c.ControllerName()
   114  	err = errors.Cause(err)
   115  	if err != nil && err != modelcmd.ErrNoControllersDefined && err != modelcmd.ErrNoCurrentController {
   116  		return errors.Trace(err)
   117  	}
   118  
   119  	var environ environs.Environ
   120  	if err == nil {
   121  		if environ, err := prepare(context, controllerName, c.ClientStore()); err == nil {
   122  			logger.Infof("creating image metadata for model %q", environ.Config().Name())
   123  			// If the user has not specified region and endpoint, try and get it from the environment.
   124  			if c.Region == "" || c.Endpoint == "" {
   125  				var cloudSpec simplestreams.CloudSpec
   126  				if inst, ok := environ.(simplestreams.HasRegion); ok {
   127  					if cloudSpec, err = inst.Region(); err != nil {
   128  						return err
   129  					}
   130  				} else {
   131  					return errors.Errorf("model %q cannot provide region and endpoint", environ.Config().Name())
   132  				}
   133  				// If only one of region or endpoint is provided, that is a problem.
   134  				if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") {
   135  					return errors.Errorf("cannot generate metadata without a complete cloud configuration")
   136  				}
   137  				if c.Region == "" {
   138  					c.Region = cloudSpec.Region
   139  				}
   140  				if c.Endpoint == "" {
   141  					c.Endpoint = cloudSpec.Endpoint
   142  				}
   143  			}
   144  			cfg := environ.Config()
   145  			if c.Series == "" {
   146  				c.Series = config.PreferredSeries(cfg)
   147  			}
   148  		} else {
   149  			logger.Warningf("bootstrap parameters could not be opened: %v", err)
   150  		}
   151  	}
   152  	if environ == nil {
   153  		logger.Infof("no model found, creating image metadata using user supplied data")
   154  	}
   155  	if c.Series == "" {
   156  		c.Series = version.SupportedLTS()
   157  	}
   158  	if c.ImageId == "" {
   159  		return errors.Errorf("image id must be specified")
   160  	}
   161  	if c.Region == "" {
   162  		return errors.Errorf("image region must be specified")
   163  	}
   164  	if c.Endpoint == "" {
   165  		return errors.Errorf("cloud endpoint URL must be specified")
   166  	}
   167  	if c.Dir == "" {
   168  		logger.Infof("no destination directory specified, using current directory")
   169  		var err error
   170  		if c.Dir, err = os.Getwd(); err != nil {
   171  			return err
   172  		}
   173  	}
   174  	return nil
   175  }
   176  
   177  var helpDoc = `
   178  Image metadata files have been written to:
   179  %s.
   180  For Juju to use this metadata, the files need to be put into the
   181  image metadata search path. There are 2 options:
   182  
   183  1. For local access, use the --metadata-source parameter when bootstrapping:
   184     juju bootstrap --metadata-source %s [...]
   185  
   186  2. For remote access, use image-metadata-url attribute for model configuration. 
   187  To set it as a default for any model or for the controller model, 
   188  it needs to be supplied as part of --model-default to 'juju bootstrap' command.
   189  See 'bootstrap' help for more details.
   190  For configuration for a particular model, set it as --image-metadata-url on
   191  'juju model-config'. See 'model-config' help for more details.
   192  Regardless of where this attribute is used, it expects a reachable URL. 
   193  You need to configure a http server to serve the contents of
   194  %s
   195  and set the value of image-metadata-url accordingly.
   196  `
   197  
   198  func (c *imageMetadataCommand) Run(context *cmd.Context) error {
   199  	if err := c.setParams(context); err != nil {
   200  		return err
   201  	}
   202  	out := context.Stdout
   203  	im := &imagemetadata.ImageMetadata{
   204  		Id:       c.ImageId,
   205  		Arch:     c.Arch,
   206  		Stream:   c.Stream,
   207  		VirtType: c.VirtType,
   208  		Storage:  c.Storage,
   209  	}
   210  	cloudSpec := simplestreams.CloudSpec{
   211  		Region:   c.Region,
   212  		Endpoint: c.Endpoint,
   213  	}
   214  	targetStorage, err := filestorage.NewFileStorageWriter(c.Dir)
   215  	if err != nil {
   216  		return err
   217  	}
   218  	err = imagemetadata.MergeAndWriteMetadata(c.Series, []*imagemetadata.ImageMetadata{im}, &cloudSpec, targetStorage)
   219  	if err != nil {
   220  		return errors.Errorf("image metadata files could not be created: %v", err)
   221  	}
   222  	dir := context.AbsPath(c.Dir)
   223  	dest := filepath.Join(dir, storage.BaseImagesPath, "streams", "v1")
   224  	fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, dir, dir))
   225  	return nil
   226  }