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