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