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