github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/cmd/plugins/juju-metadata/imagemetadata.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 11 "launchpad.net/gnuflag" 12 13 "github.com/juju/juju/cmd" 14 "github.com/juju/juju/cmd/envcmd" 15 "github.com/juju/juju/environs" 16 "github.com/juju/juju/environs/config" 17 "github.com/juju/juju/environs/configstore" 18 "github.com/juju/juju/environs/filestorage" 19 "github.com/juju/juju/environs/imagemetadata" 20 "github.com/juju/juju/environs/simplestreams" 21 "github.com/juju/juju/environs/storage" 22 "github.com/juju/juju/juju/arch" 23 ) 24 25 // ImageMetadataCommand is used to write out simplestreams image metadata information. 26 type ImageMetadataCommand struct { 27 envcmd.EnvCommandBase 28 Dir string 29 Series string 30 Arch string 31 ImageId string 32 Region string 33 Endpoint string 34 privateStorage string 35 } 36 37 var imageMetadataDoc = ` 38 generate-image creates simplestreams image metadata for 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 43 Using command arguments, it is possible to override cloud attributes region, endpoint, and series. 44 By default, "amd64" is used for the architecture but this may also be changed. 45 ` 46 47 func (c *ImageMetadataCommand) Info() *cmd.Info { 48 return &cmd.Info{ 49 Name: "generate-image", 50 Purpose: "generate simplestreams image metadata", 51 Doc: imageMetadataDoc, 52 } 53 } 54 55 func (c *ImageMetadataCommand) SetFlags(f *gnuflag.FlagSet) { 56 f.StringVar(&c.Series, "s", "", "the charm series") 57 f.StringVar(&c.Arch, "a", arch.AMD64, "the image achitecture") 58 f.StringVar(&c.Dir, "d", "", "the destination directory in which to place the metadata files") 59 f.StringVar(&c.ImageId, "i", "", "the image id") 60 f.StringVar(&c.Region, "r", "", "the region") 61 f.StringVar(&c.Endpoint, "u", "", "the cloud endpoint (for Openstack, this is the Identity Service endpoint)") 62 } 63 64 // setParams sets parameters based on the environment configuration 65 // for those which have not been explicitly specified. 66 func (c *ImageMetadataCommand) setParams(context *cmd.Context) error { 67 c.privateStorage = "<private storage name>" 68 var environ environs.Environ 69 if store, err := configstore.Default(); err == nil { 70 if environ, err = environs.PrepareFromName(c.EnvName, context, store); err == nil { 71 logger.Infof("creating image metadata for environment %q", environ.Name()) 72 // If the user has not specified region and endpoint, try and get it from the environment. 73 if c.Region == "" || c.Endpoint == "" { 74 var cloudSpec simplestreams.CloudSpec 75 if inst, ok := environ.(simplestreams.HasRegion); ok { 76 if cloudSpec, err = inst.Region(); err != nil { 77 return err 78 } 79 } else { 80 return fmt.Errorf("environment %q cannot provide region and endpoint", environ.Name()) 81 } 82 // If only one of region or endpoint is provided, that is a problem. 83 if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") { 84 return fmt.Errorf("cannot generate metadata without a complete cloud configuration") 85 } 86 if c.Region == "" { 87 c.Region = cloudSpec.Region 88 } 89 if c.Endpoint == "" { 90 c.Endpoint = cloudSpec.Endpoint 91 } 92 } 93 cfg := environ.Config() 94 if c.Series == "" { 95 c.Series = config.PreferredSeries(cfg) 96 } 97 if v, ok := cfg.AllAttrs()["control-bucket"]; ok { 98 c.privateStorage = v.(string) 99 } 100 } else { 101 logger.Warningf("environment %q could not be opened: %v", c.EnvName, err) 102 } 103 } 104 if environ == nil { 105 logger.Infof("no environment found, creating image metadata using user supplied data") 106 } 107 if c.Series == "" { 108 c.Series = config.LatestLtsSeries() 109 } 110 if c.ImageId == "" { 111 return fmt.Errorf("image id must be specified") 112 } 113 if c.Region == "" { 114 return fmt.Errorf("image region must be specified") 115 } 116 if c.Endpoint == "" { 117 return fmt.Errorf("cloud endpoint URL must be specified") 118 } 119 if c.Dir == "" { 120 logger.Infof("no destination directory specified, using current directory") 121 var err error 122 if c.Dir, err = os.Getwd(); err != nil { 123 return err 124 } 125 } 126 return nil 127 } 128 129 var helpDoc = ` 130 image metadata files have been written to: 131 %s. 132 For Juju to use this metadata, the files need to be put into the 133 image metadata search path. There are 2 options: 134 135 1. Use the --metadata-source parameter when bootstrapping: 136 juju bootstrap --metadata-source %s 137 138 2. Use image-metadata-url in $JUJU_HOME/environments.yaml 139 Configure a http server to serve the contents of 140 %s 141 and set the value of image-metadata-url accordingly. 142 143 " 144 145 ` 146 147 func (c *ImageMetadataCommand) Run(context *cmd.Context) error { 148 if err := c.setParams(context); err != nil { 149 return err 150 } 151 out := context.Stdout 152 im := &imagemetadata.ImageMetadata{ 153 Id: c.ImageId, 154 Arch: c.Arch, 155 } 156 cloudSpec := simplestreams.CloudSpec{ 157 Region: c.Region, 158 Endpoint: c.Endpoint, 159 } 160 targetStorage, err := filestorage.NewFileStorageWriter(c.Dir) 161 if err != nil { 162 return err 163 } 164 err = imagemetadata.MergeAndWriteMetadata(c.Series, []*imagemetadata.ImageMetadata{im}, &cloudSpec, targetStorage) 165 if err != nil { 166 return fmt.Errorf("image metadata files could not be created: %v", err) 167 } 168 dir := context.AbsPath(c.Dir) 169 dest := filepath.Join(dir, storage.BaseImagesPath, "streams", "v1") 170 fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, dir, dir)) 171 return nil 172 }