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