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 }