github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/plugins/juju-metadata/validateimagemetadata.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  	"bytes"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/juju/cmd"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/gnuflag"
    16  	"github.com/juju/utils"
    17  
    18  	"github.com/juju/juju/cmd/modelcmd"
    19  	"github.com/juju/juju/cmd/output"
    20  	"github.com/juju/juju/environs"
    21  	"github.com/juju/juju/environs/config"
    22  	"github.com/juju/juju/environs/imagemetadata"
    23  	"github.com/juju/juju/environs/simplestreams"
    24  )
    25  
    26  func newValidateImageMetadataCommand() cmd.Command {
    27  	return modelcmd.Wrap(&validateImageMetadataCommand{})
    28  }
    29  
    30  // validateImageMetadataCommand
    31  type validateImageMetadataCommand struct {
    32  	imageMetadataCommandBase
    33  	out          cmd.Output
    34  	providerType string
    35  	metadataDir  string
    36  	series       string
    37  	region       string
    38  	endpoint     string
    39  	stream       string
    40  }
    41  
    42  var validateImagesMetadataDoc = `
    43  validate-images loads simplestreams metadata and validates the contents by
    44  looking for images belonging to the specified cloud.
    45  
    46  The cloud specification comes from the current Juju model, as specified in
    47  the usual way from either the -m option, or JUJU_MODEL. Series, Region, and
    48  Endpoint are the key attributes.
    49  
    50  The key model attributes may be overridden using command arguments, so
    51  that the validation may be peformed on arbitary metadata.
    52  
    53  Examples:
    54  
    55   - validate using the current model settings but with series raring
    56  
    57    juju metadata validate-images -s raring
    58  
    59   - validate using the current model settings but with series raring and
    60   using metadata from local directory (the directory is expected to have an
    61   "images" subdirectory containing the metadata, and corresponds to the parameter
    62   passed to the image metadata generatation command).
    63  
    64    juju metadata validate-images -s raring -d <some directory>
    65  
    66  A key use case is to validate newly generated metadata prior to deployment to
    67  production. In this case, the metadata is placed in a local directory, a cloud
    68  provider type is specified (ec2, openstack etc), and the validation is performed
    69  for each supported region and series.
    70  
    71  Example bash snippet:
    72  
    73  #!/bin/bash
    74  
    75  juju metadata validate-images -p ec2 -r us-east-1 -s precise -d <some directory>
    76  RETVAL=$?
    77  [ $RETVAL -eq 0 ] && echo Success
    78  [ $RETVAL -ne 0 ] && echo Failure
    79  `
    80  
    81  func (c *validateImageMetadataCommand) Info() *cmd.Info {
    82  	return &cmd.Info{
    83  		Name:    "validate-images",
    84  		Purpose: "validate image metadata and ensure image(s) exist for a model",
    85  		Doc:     validateImagesMetadataDoc,
    86  	}
    87  }
    88  
    89  func (c *validateImageMetadataCommand) SetFlags(f *gnuflag.FlagSet) {
    90  	c.out.AddFlags(f, "yaml", output.DefaultFormatters)
    91  	f.StringVar(&c.providerType, "p", "", "the provider type eg ec2, openstack")
    92  	f.StringVar(&c.metadataDir, "d", "", "directory where metadata files are found")
    93  	f.StringVar(&c.series, "s", "", "the series for which to validate (overrides env config series)")
    94  	f.StringVar(&c.region, "r", "", "the region for which to validate (overrides env config region)")
    95  	f.StringVar(&c.endpoint, "u", "", "the cloud endpoint URL for which to validate (overrides env config endpoint)")
    96  	f.StringVar(&c.stream, "stream", "", "the images stream (defaults to released)")
    97  }
    98  
    99  func (c *validateImageMetadataCommand) Init(args []string) error {
   100  	if c.providerType != "" {
   101  		if c.series == "" {
   102  			return errors.Errorf("series required if provider type is specified")
   103  		}
   104  		if c.region == "" {
   105  			return errors.Errorf("region required if provider type is specified")
   106  		}
   107  		if c.metadataDir == "" {
   108  			return errors.Errorf("metadata directory required if provider type is specified")
   109  		}
   110  	}
   111  	return cmd.CheckEmpty(args)
   112  }
   113  
   114  var _ environs.ConfigGetter = (*overrideEnvStream)(nil)
   115  
   116  // overrideEnvStream implements environs.ConfigGetter and
   117  // ensures that the environs.Config returned by Config()
   118  // has the specified stream.
   119  type overrideEnvStream struct {
   120  	environs.Environ
   121  	stream string
   122  }
   123  
   124  func (oes *overrideEnvStream) Config() *config.Config {
   125  	cfg := oes.Environ.Config()
   126  	// If no stream specified, just use default from environ.
   127  	if oes.stream == "" {
   128  		return cfg
   129  	}
   130  	newCfg, err := cfg.Apply(map[string]interface{}{"image-stream": oes.stream})
   131  	if err != nil {
   132  		// This should never happen.
   133  		panic(errors.Errorf("unexpected error making override config: %v", err))
   134  	}
   135  	return newCfg
   136  }
   137  
   138  func (c *validateImageMetadataCommand) Run(context *cmd.Context) error {
   139  	params, err := c.createLookupParams(context)
   140  	if err != nil {
   141  		return err
   142  	}
   143  
   144  	image_ids, resolveInfo, err := imagemetadata.ValidateImageMetadata(params)
   145  	if err != nil {
   146  		if resolveInfo != nil {
   147  			metadata := map[string]interface{}{
   148  				"Resolve Metadata": *resolveInfo,
   149  			}
   150  			buff := &bytes.Buffer{}
   151  			if yamlErr := cmd.FormatYaml(buff, metadata); yamlErr == nil {
   152  				err = errors.Errorf("%v\n%v", err, buff.String())
   153  			}
   154  		}
   155  		return err
   156  	}
   157  	if len(image_ids) > 0 {
   158  		metadata := map[string]interface{}{
   159  			"ImageIds":         image_ids,
   160  			"Region":           params.Region,
   161  			"Resolve Metadata": *resolveInfo,
   162  		}
   163  		c.out.Write(context, metadata)
   164  	} else {
   165  		var sources []string
   166  		for _, s := range params.Sources {
   167  			url, err := s.URL("")
   168  			if err == nil {
   169  				sources = append(sources, fmt.Sprintf("- %s (%s)", s.Description(), url))
   170  			}
   171  		}
   172  		return errors.Errorf(
   173  			"no matching image ids for region %s using sources:\n%s",
   174  			params.Region, strings.Join(sources, "\n"))
   175  	}
   176  	return nil
   177  }
   178  
   179  func (c *validateImageMetadataCommand) createLookupParams(context *cmd.Context) (*simplestreams.MetadataLookupParams, error) {
   180  	params := &simplestreams.MetadataLookupParams{Stream: c.stream}
   181  
   182  	if c.providerType == "" {
   183  		environ, err := c.prepare(context)
   184  		if err != nil {
   185  			return nil, err
   186  		}
   187  		mdLookup, ok := environ.(simplestreams.MetadataValidator)
   188  		if !ok {
   189  			return nil, errors.Errorf("%s provider does not support image metadata validation", environ.Config().Type())
   190  		}
   191  		params, err = mdLookup.MetadataLookupParams(c.region)
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		oes := &overrideEnvStream{environ, c.stream}
   196  		params.Sources, err = environs.ImageMetadataSources(oes)
   197  		if err != nil {
   198  			return nil, err
   199  		}
   200  	} else {
   201  		prov, err := environs.Provider(c.providerType)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		mdLookup, ok := prov.(simplestreams.MetadataValidator)
   206  		if !ok {
   207  			return nil, errors.Errorf("%s provider does not support image metadata validation", c.providerType)
   208  		}
   209  		params, err = mdLookup.MetadataLookupParams(c.region)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  	}
   214  
   215  	if c.series != "" {
   216  		params.Series = c.series
   217  	}
   218  	if c.region != "" {
   219  		params.Region = c.region
   220  	}
   221  	if c.endpoint != "" {
   222  		params.Endpoint = c.endpoint
   223  	}
   224  	if c.metadataDir != "" {
   225  		dir := filepath.Join(c.metadataDir, "images")
   226  		if _, err := os.Stat(dir); err != nil {
   227  			return nil, err
   228  		}
   229  		params.Sources = imagesDataSources(dir)
   230  	}
   231  	return params, nil
   232  }
   233  
   234  var imagesDataSources = func(urls ...string) []simplestreams.DataSource {
   235  	dataSources := make([]simplestreams.DataSource, len(urls))
   236  	publicKey, _ := simplestreams.UserPublicSigningKey()
   237  	for i, url := range urls {
   238  		dataSources[i] = simplestreams.NewURLSignedDataSource(
   239  			"local metadata directory", "file://"+url, publicKey, utils.VerifySSLHostnames, simplestreams.CUSTOM_CLOUD_DATA, false)
   240  	}
   241  	return dataSources
   242  }