github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 "github.com/juju/cmd" 12 "github.com/juju/errors" 13 "github.com/juju/utils/arch" 14 "github.com/juju/utils/series" 15 "launchpad.net/gnuflag" 16 17 "github.com/juju/juju/cmd/modelcmd" 18 "github.com/juju/juju/environs" 19 "github.com/juju/juju/environs/config" 20 "github.com/juju/juju/environs/filestorage" 21 "github.com/juju/juju/environs/imagemetadata" 22 "github.com/juju/juju/environs/simplestreams" 23 "github.com/juju/juju/environs/storage" 24 ) 25 26 type imageMetadataCommandBase struct { 27 modelcmd.ModelCommandBase 28 } 29 30 func (c *imageMetadataCommandBase) prepare(context *cmd.Context) (environs.Environ, error) { 31 // NOTE(axw) this is a work-around for the TODO below. This 32 // means that the command will only work if you've bootstrapped 33 // the specified environment. 34 cfg, err := modelcmd.NewGetBootstrapConfigFunc(c.ClientStore())(c.ControllerName()) 35 if err != nil { 36 return nil, errors.Trace(err) 37 } 38 // TODO(axw) we'll need to revise the metadata commands to work 39 // without preparing an environment. They should take the same 40 // format as bootstrap, i.e. cloud/region, and we'll use that to 41 // identify region and endpoint info that we need. Not sure what 42 // we'll do about simplestreams.MetadataValidator yet. Probably 43 // move it to the EnvironProvider interface. 44 return environs.New(cfg) 45 } 46 47 func newImageMetadataCommand() cmd.Command { 48 return modelcmd.Wrap(&imageMetadataCommand{}) 49 } 50 51 // imageMetadataCommand is used to write out simplestreams image metadata information. 52 type imageMetadataCommand struct { 53 imageMetadataCommandBase 54 Dir string 55 Series string 56 Arch string 57 ImageId string 58 Region string 59 Endpoint string 60 Stream string 61 VirtType string 62 Storage string 63 privateStorage string 64 } 65 66 var imageMetadataDoc = ` 67 generate-image creates simplestreams image metadata for the specified cloud. 68 69 The cloud specification comes from the current Juju model, as specified in 70 the usual way from either the -m option, or JUJU_MODEL. 71 72 Using command arguments, it is possible to override cloud attributes region, endpoint, and series. 73 By default, "amd64" is used for the architecture but this may also be changed. 74 ` 75 76 func (c *imageMetadataCommand) Info() *cmd.Info { 77 return &cmd.Info{ 78 Name: "generate-image", 79 Purpose: "generate simplestreams image metadata", 80 Doc: imageMetadataDoc, 81 } 82 } 83 84 func (c *imageMetadataCommand) SetFlags(f *gnuflag.FlagSet) { 85 f.StringVar(&c.Series, "s", "", "the charm series") 86 f.StringVar(&c.Arch, "a", arch.AMD64, "the image achitecture") 87 f.StringVar(&c.Dir, "d", "", "the destination directory in which to place the metadata files") 88 f.StringVar(&c.ImageId, "i", "", "the image id") 89 f.StringVar(&c.Region, "r", "", "the region") 90 f.StringVar(&c.Endpoint, "u", "", "the cloud endpoint (for Openstack, this is the Identity Service endpoint)") 91 f.StringVar(&c.Stream, "stream", imagemetadata.ReleasedStream, "the image stream") 92 f.StringVar(&c.VirtType, "virt-type", "", "the image virtualisation type") 93 f.StringVar(&c.Storage, "storage", "", "the type of root storage") 94 } 95 96 // setParams sets parameters based on the environment configuration 97 // for those which have not been explicitly specified. 98 func (c *imageMetadataCommand) setParams(context *cmd.Context) error { 99 c.privateStorage = "<private storage name>" 100 var environ environs.Environ 101 if environ, err := c.prepare(context); err == nil { 102 logger.Infof("creating image metadata for model %q", environ.Config().Name()) 103 // If the user has not specified region and endpoint, try and get it from the environment. 104 if c.Region == "" || c.Endpoint == "" { 105 var cloudSpec simplestreams.CloudSpec 106 if inst, ok := environ.(simplestreams.HasRegion); ok { 107 if cloudSpec, err = inst.Region(); err != nil { 108 return err 109 } 110 } else { 111 return errors.Errorf("model %q cannot provide region and endpoint", environ.Config().Name()) 112 } 113 // If only one of region or endpoint is provided, that is a problem. 114 if cloudSpec.Region != cloudSpec.Endpoint && (cloudSpec.Region == "" || cloudSpec.Endpoint == "") { 115 return errors.Errorf("cannot generate metadata without a complete cloud configuration") 116 } 117 if c.Region == "" { 118 c.Region = cloudSpec.Region 119 } 120 if c.Endpoint == "" { 121 c.Endpoint = cloudSpec.Endpoint 122 } 123 } 124 cfg := environ.Config() 125 if c.Series == "" { 126 c.Series = config.PreferredSeries(cfg) 127 } 128 } else { 129 logger.Warningf("model could not be opened: %v", err) 130 } 131 if environ == nil { 132 logger.Infof("no model found, creating image metadata using user supplied data") 133 } 134 if c.Series == "" { 135 c.Series = series.LatestLts() 136 } 137 if c.ImageId == "" { 138 return errors.Errorf("image id must be specified") 139 } 140 if c.Region == "" { 141 return errors.Errorf("image region must be specified") 142 } 143 if c.Endpoint == "" { 144 return errors.Errorf("cloud endpoint URL must be specified") 145 } 146 if c.Dir == "" { 147 logger.Infof("no destination directory specified, using current directory") 148 var err error 149 if c.Dir, err = os.Getwd(); err != nil { 150 return err 151 } 152 } 153 return nil 154 } 155 156 var helpDoc = ` 157 Image metadata files have been written to: 158 %s. 159 For Juju to use this metadata, the files need to be put into the 160 image metadata search path. There are 2 options: 161 162 1. Use the --metadata-source parameter when bootstrapping: 163 juju bootstrap --metadata-source %s 164 165 2. Use image-metadata-url in $JUJU_DATA/environments.yaml 166 (if $JUJU_DATA is not set it will try $XDG_DATA_HOME/juju and 167 if not set either default to ~/.local/share/juju) 168 Configure a http server to serve the contents of 169 %s 170 and set the value of image-metadata-url accordingly. 171 ` 172 173 func (c *imageMetadataCommand) Run(context *cmd.Context) error { 174 if err := c.setParams(context); err != nil { 175 return err 176 } 177 out := context.Stdout 178 im := &imagemetadata.ImageMetadata{ 179 Id: c.ImageId, 180 Arch: c.Arch, 181 Stream: c.Stream, 182 VirtType: c.VirtType, 183 Storage: c.Storage, 184 } 185 cloudSpec := simplestreams.CloudSpec{ 186 Region: c.Region, 187 Endpoint: c.Endpoint, 188 } 189 targetStorage, err := filestorage.NewFileStorageWriter(c.Dir) 190 if err != nil { 191 return err 192 } 193 err = imagemetadata.MergeAndWriteMetadata(c.Series, []*imagemetadata.ImageMetadata{im}, &cloudSpec, targetStorage) 194 if err != nil { 195 return fmt.Errorf("image metadata files could not be created: %v", err) 196 } 197 dir := context.AbsPath(c.Dir) 198 dest := filepath.Join(dir, storage.BaseImagesPath, "streams", "v1") 199 fmt.Fprintf(out, fmt.Sprintf(helpDoc, dest, dir, dir)) 200 return nil 201 }