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 }