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