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