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 }