func (t *ToolsMetadata) productId() (string, error) { seriesVersion, err := series.SeriesVersion(t.Release) if err != nil { return "", err } return fmt.Sprintf("com.ubuntu.juju:%s:%s", seriesVersion, t.Arch), nil }
func validateMetadata(m *imagesMetadataDoc) error { // series must be supplied. if m.Series == "" { return errors.NotValidf("missing series: metadata for image %v", m.ImageId) } v, err := series.SeriesVersion(m.Series) if err != nil { return err } m.Version = v if m.Stream == "" { return errors.NotValidf("missing stream: metadata for image %v", m.ImageId) } if m.Source == "" { return errors.NotValidf("missing source: metadata for image %v", m.ImageId) } if m.Arch == "" { return errors.NotValidf("missing architecture: metadata for image %v", m.ImageId) } if m.Region == "" { return errors.NotValidf("missing region: metadata for image %v", m.ImageId) } return nil }
// Init implements Command.Init. func (c *addImageMetadataCommand) validate() error { if c.Series != "" { if _, err := series.SeriesVersion(c.Series); err != nil { return errors.Trace(err) } } return nil }
func (s *supportedSeriesWindowsSuite) TestSeriesVersion(c *gc.C) { vers, err := series.SeriesVersion("win8") if err != nil { c.Assert(err, gc.Not(gc.ErrorMatches), `invalid series "win8"`, gc.Commentf(`unable to lookup series "win8"`)) } else { c.Assert(err, jc.ErrorIsNil) } c.Assert(err, jc.ErrorIsNil) c.Assert(vers, gc.Equals, "win8") }
func (tc *testConstraint) ProductIds() ([]string, error) { version, err := series.SeriesVersion(tc.Series[0]) if err != nil { return nil, err } ids := make([]string, len(tc.Arches)) for i, arch := range tc.Arches { ids[i] = fmt.Sprintf("com.ubuntu.cloud:server:%s:%s", version, arch) } return ids, nil }
func (s *supportedSeriesSuite) TestSeriesVersion(c *gc.C) { // There is no distro-info on Windows or CentOS. if os.HostOS() != os.Ubuntu { c.Skip("This test is only relevant on Ubuntu.") } vers, err := series.SeriesVersion("precise") if err != nil && err.Error() == `invalid series "precise"` { c.Fatalf(`Unable to lookup series "precise", you may need to: apt-get install distro-info`) } c.Assert(err, jc.ErrorIsNil) c.Assert(vers, gc.Equals, "12.04") }
// cloneToolsForSeries copies the built tools tarball into a tarball for the specified // stream and series and generates corresponding metadata. func cloneToolsForSeries(toolsInfo *BuiltTools, stream string, series ...string) error { // Copy the tools to the target storage, recording a Tools struct for each one. var targetTools coretools.List targetTools = append(targetTools, &coretools.Tools{ Version: toolsInfo.Version, Size: toolsInfo.Size, SHA256: toolsInfo.Sha256Hash, }) putTools := func(vers version.Binary) (string, error) { name := envtools.StorageName(vers, stream) src := filepath.Join(toolsInfo.Dir, toolsInfo.StorageName) dest := filepath.Join(toolsInfo.Dir, name) destDir := filepath.Dir(dest) if err := os.MkdirAll(destDir, 0755); err != nil { return "", err } if err := utils.CopyFile(dest, src); err != nil { return "", err } // Append to targetTools the attributes required to write out tools metadata. targetTools = append(targetTools, &coretools.Tools{ Version: vers, Size: toolsInfo.Size, SHA256: toolsInfo.Sha256Hash, }) return name, nil } logger.Debugf("generating tarballs for %v", series) for _, series := range series { _, err := jujuseries.SeriesVersion(series) if err != nil { return err } if series != toolsInfo.Version.Series { fakeVersion := toolsInfo.Version fakeVersion.Series = series if _, err := putTools(fakeVersion); err != nil { return err } } } // The tools have been copied to a temp location from which they will be uploaded, // now write out the matching simplestreams metadata so that SyncTools can find them. metadataStore, err := filestorage.NewFileStorageWriter(toolsInfo.Dir) if err != nil { return err } logger.Debugf("generating tools metadata") return envtools.MergeAndWriteMetadata(metadataStore, stream, stream, targetTools, false) }
// MergeAndWriteMetadata reads the existing metadata from storage (if any), // and merges it with supplied metadata, writing the resulting metadata is written to storage. func MergeAndWriteMetadata(ser string, metadata []*ImageMetadata, cloudSpec *simplestreams.CloudSpec, metadataStore storage.Storage) error { existingMetadata, err := readMetadata(metadataStore) if err != nil { return err } seriesVersion, err := series.SeriesVersion(ser) if err != nil { return err } toWrite, allCloudSpec := mergeMetadata(seriesVersion, cloudSpec, metadata, existingMetadata) return writeMetadata(toWrite, allCloudSpec, metadataStore) }
func validateMetadata(m *imagesMetadataDoc) error { // series must be supplied. if m.Series == "" { return errors.NotValidf("missing series: metadata for image %v", m.ImageId) } v, err := series.SeriesVersion(m.Series) if err != nil { return err } m.Version = v return nil }
// ProductIds generates a string array representing product ids formed similarly to an ISCSI qualified name (IQN). func (ic *ImageConstraint) ProductIds() ([]string, error) { stream := idStream(ic.Stream) nrArches := len(ic.Arches) nrSeries := len(ic.Series) ids := make([]string, nrArches*nrSeries) for i, arch := range ic.Arches { for j, ser := range ic.Series { version, err := series.SeriesVersion(ser) if err != nil { return nil, err } ids[j*nrArches+i] = fmt.Sprintf("com.ubuntu.cloud%s:server:%s:%s", stream, version, arch) } } return ids, nil }
// ubuntuSKU returns the best SKU for the Canonical:UbuntuServer offering, // matching the given series. func ubuntuSKU(series, stream, location string, client compute.VirtualMachineImagesClient) (string, error) { seriesVersion, err := jujuseries.SeriesVersion(series) if err != nil { return "", errors.Trace(err) } logger.Debugf("listing SKUs: Location=%s, Publisher=%s, Offer=%s", location, ubuntuPublisher, ubuntuOffering) result, err := client.ListSkus(location, ubuntuPublisher, ubuntuOffering) if err != nil { return "", errors.Annotate(err, "listing Ubuntu SKUs") } if result.Value == nil || len(*result.Value) == 0 { return "", errors.NotFoundf("Ubuntu SKUs") } skuNamesByVersion := make(map[ubuntuVersion]string) var versions ubuntuVersions for _, result := range *result.Value { skuName := to.String(result.Name) if !strings.HasPrefix(skuName, seriesVersion) { logger.Debugf("ignoring SKU %q (does not match series %q)", skuName, series) continue } version, tag, err := parseUbuntuSKU(skuName) if err != nil { logger.Errorf("ignoring SKU %q (failed to parse: %s)", skuName, err) continue } var skuStream string switch tag { case "", "LTS": skuStream = imagemetadata.ReleasedStream case "DAILY", "DAILY-LTS": skuStream = dailyStream } if skuStream == "" || skuStream != stream { logger.Debugf("ignoring SKU %q (not in %q stream)", skuName, stream) continue } skuNamesByVersion[version] = skuName versions = append(versions, version) } if len(versions) == 0 { return "", errors.NotFoundf("Ubuntu SKUs for %s stream", stream) } sort.Sort(versions) bestVersion := versions[len(versions)-1] return skuNamesByVersion[bestVersion], nil }
// ProductIds generates a string array representing product ids formed similarly to an ISCSI qualified name (IQN). func (tc *ToolsConstraint) ProductIds() ([]string, error) { var allIds []string for _, ser := range tc.Series { version, err := series.SeriesVersion(ser) if err != nil { if series.IsUnknownSeriesVersionError(err) { logger.Debugf("ignoring unknown series %q", ser) continue } return nil, err } ids := make([]string, len(tc.Arches)) for i, arch := range tc.Arches { ids[i] = fmt.Sprintf("com.ubuntu.juju:%s:%s", version, arch) } allIds = append(allIds, ids...) } return allIds, nil }
func filterImageMetadata( c *gc.C, in []*imagemetadata.ImageMetadata, filterSeries string, filterArches []string, ) []*imagemetadata.ImageMetadata { var imageMetadata []*imagemetadata.ImageMetadata for _, im := range in { version, err := series.SeriesVersion(filterSeries) c.Assert(err, jc.ErrorIsNil) if im.Version != version { continue } match := false for _, arch := range filterArches { match = match || im.Arch == arch } if match { imageMetadata = append(imageMetadata, im) } } return imageMetadata }
func (s *providerSuite) makeTestMetadata(c *gc.C, ser, location string, im []*imagemetadata.ImageMetadata) { cloudSpec := simplestreams.CloudSpec{ Region: location, Endpoint: "https://management.core.windows.net/", } seriesVersion, err := series.SeriesVersion(ser) c.Assert(err, jc.ErrorIsNil) for _, im := range im { im.Version = seriesVersion im.RegionName = cloudSpec.Region im.Endpoint = cloudSpec.Endpoint } index, products, err := imagemetadata.MarshalImageMetadataJSON( im, []simplestreams.CloudSpec{cloudSpec}, time.Now(), ) c.Assert(err, jc.ErrorIsNil) files := map[string]string{ "/streams/v1/index.json": string(index), "/" + imagemetadata.ProductMetadataPath: string(products), } s.PatchValue(&testRoundTripper.Sub, jujutest.NewCannedRoundTripper(files, nil)) }
// ParseMetadataFromStorage loads ToolsMetadata from the specified storage reader. func ParseMetadataFromStorage(c *gc.C, stor storage.StorageReader, stream string, expectMirrors bool) []*tools.ToolsMetadata { source := storage.NewStorageSimpleStreamsDataSource("test storage reader", stor, "tools") params := simplestreams.ValueParams{ DataType: tools.ContentDownload, ValueTemplate: tools.ToolsMetadata{}, } const requireSigned = false indexPath := simplestreams.UnsignedIndex("v1", 2) mirrorsPath := simplestreams.MirrorsPath("v1") indexRef, err := simplestreams.GetIndexWithFormat( source, indexPath, "index:1.0", mirrorsPath, requireSigned, simplestreams.CloudSpec{}, params) c.Assert(err, jc.ErrorIsNil) toolsIndexMetadata := indexRef.Indexes[tools.ToolsContentId(stream)] c.Assert(toolsIndexMetadata, gc.NotNil) // Read the products file contents. r, err := stor.Get(path.Join("tools", toolsIndexMetadata.ProductsFilePath)) defer r.Close() c.Assert(err, jc.ErrorIsNil) data, err := ioutil.ReadAll(r) c.Assert(err, jc.ErrorIsNil) url, err := source.URL(toolsIndexMetadata.ProductsFilePath) c.Assert(err, jc.ErrorIsNil) cloudMetadata, err := simplestreams.ParseCloudMetadata(data, "products:1.0", url, tools.ToolsMetadata{}) c.Assert(err, jc.ErrorIsNil) toolsMetadataMap := make(map[string]*tools.ToolsMetadata) expectedProductIds := make(set.Strings) toolsVersions := make(set.Strings) for _, mc := range cloudMetadata.Products { for _, items := range mc.Items { for key, item := range items.Items { toolsMetadata := item.(*tools.ToolsMetadata) toolsMetadataMap[key] = toolsMetadata toolsVersions.Add(key) seriesVersion, err := series.SeriesVersion(toolsMetadata.Release) if err != nil { c.Assert(err, jc.Satisfies, series.IsUnknownSeriesVersionError) } productId := fmt.Sprintf("com.ubuntu.juju:%s:%s", seriesVersion, toolsMetadata.Arch) expectedProductIds.Add(productId) } } } // Make sure index's product IDs are all represented in the products metadata. sort.Strings(toolsIndexMetadata.ProductIds) c.Assert(toolsIndexMetadata.ProductIds, gc.DeepEquals, expectedProductIds.SortedValues()) toolsMetadata := make([]*tools.ToolsMetadata, len(toolsMetadataMap)) for i, key := range toolsVersions.SortedValues() { toolsMetadata[i] = toolsMetadataMap[key] } if expectMirrors { r, err = stor.Get(path.Join("tools", simplestreams.UnsignedMirror("v1"))) c.Assert(err, jc.ErrorIsNil) defer r.Close() data, err = ioutil.ReadAll(r) c.Assert(err, jc.ErrorIsNil) c.Assert(string(data), jc.Contains, `"mirrors":`) c.Assert(string(data), jc.Contains, tools.ToolsContentId(stream)) c.Assert(err, jc.ErrorIsNil) } return toolsMetadata }
// BootstrapInstance creates a new instance with the series of its choice, // constrained to those of the available tools, and // returns the instance result, series, and a function that // must be called to finalize the bootstrap process by transferring // the tools and installing the initial Juju controller. // This method is called by Bootstrap above, which implements environs.Bootstrap, but // is also exported so that providers can manipulate the started instance. func BootstrapInstance(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams, ) (_ *environs.StartInstanceResult, selectedSeries string, _ environs.BootstrapFinalizer, err error) { // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to make sure that only one succeeds. // First thing, ensure we have tools otherwise there's no point. if args.BootstrapSeries != "" { selectedSeries = args.BootstrapSeries } else { selectedSeries = config.PreferredSeries(env.Config()) } availableTools, err := args.AvailableTools.Match(coretools.Filter{ Series: selectedSeries, }) if err != nil { return nil, "", nil, err } // Filter image metadata to the selected series. var imageMetadata []*imagemetadata.ImageMetadata seriesVersion, err := series.SeriesVersion(selectedSeries) if err != nil { return nil, "", nil, errors.Trace(err) } for _, m := range args.ImageMetadata { if m.Version != seriesVersion { continue } imageMetadata = append(imageMetadata, m) } // Get the bootstrap SSH client. Do this early, so we know // not to bother with any of the below if we can't finish the job. client := ssh.DefaultClient if client == nil { // This should never happen: if we don't have OpenSSH, then // go.crypto/ssh should be used with an auto-generated key. return nil, "", nil, fmt.Errorf("no SSH client available") } publicKey, err := simplestreams.UserPublicSigningKey() if err != nil { return nil, "", nil, err } envCfg := env.Config() instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.ControllerConfig, args.BootstrapConstraints, args.ModelConstraints, selectedSeries, publicKey, ) if err != nil { return nil, "", nil, err } instanceConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() instanceConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() instanceConfig.Tags = instancecfg.InstanceTags(envCfg.UUID(), args.ControllerConfig.ControllerUUID(), envCfg, instanceConfig.Jobs) maybeSetBridge := func(icfg *instancecfg.InstanceConfig) { // If we need to override the default bridge name, do it now. When // args.ContainerBridgeName is empty, the default names for LXC // (lxcbr0) and KVM (virbr0) will be used. if args.ContainerBridgeName != "" { logger.Debugf("using %q as network bridge for all container types", args.ContainerBridgeName) if icfg.AgentEnvironment == nil { icfg.AgentEnvironment = make(map[string]string) } icfg.AgentEnvironment[agent.LxcBridge] = args.ContainerBridgeName } } maybeSetBridge(instanceConfig) cloudRegion := args.CloudName if args.CloudRegion != "" { cloudRegion += "/" + args.CloudRegion } fmt.Fprintf(ctx.GetStderr(), "Launching controller instance(s) on %s...\n", cloudRegion) // Print instance status reports status changes during provisioning. // Note the carriage returns, meaning subsequent prints are to the same // line of stderr, not a new line. instanceStatus := func(settableStatus status.Status, info string, data map[string]interface{}) error { // The data arg is not expected to be used in this case, but // print it, rather than ignore it, if we get something. dataString := "" if len(data) > 0 { dataString = fmt.Sprintf(" %v", data) } fmt.Fprintf(ctx.GetStderr(), " - %s%s\r", info, dataString) return nil } // Likely used after the final instanceStatus call to white-out the // current stderr line before the next use, removing any residual status // reporting output. statusCleanup := func(info string) error { // The leading spaces account for the leading characters // emitted by instanceStatus above. fmt.Fprintf(ctx.GetStderr(), " %s\r", info) return nil } result, err := env.StartInstance(environs.StartInstanceParams{ ControllerUUID: args.ControllerConfig.ControllerUUID(), Constraints: args.BootstrapConstraints, Tools: availableTools, InstanceConfig: instanceConfig, Placement: args.Placement, ImageMetadata: imageMetadata, StatusCallback: instanceStatus, CleanupCallback: statusCleanup, }) if err != nil { return nil, "", nil, errors.Annotate(err, "cannot start bootstrap instance") } // We need some padding below to overwrite any previous messages. We'll use a width of 40. msg := fmt.Sprintf(" - %s", result.Instance.Id()) if len(msg) < 40 { padding := make([]string, 40-len(msg)) msg += strings.Join(padding, " ") } fmt.Fprintln(ctx.GetStderr(), msg) finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig, opts environs.BootstrapDialOpts) error { icfg.Bootstrap.BootstrapMachineInstanceId = result.Instance.Id() icfg.Bootstrap.BootstrapMachineHardwareCharacteristics = result.Hardware envConfig := env.Config() if result.Config != nil { updated, err := envConfig.Apply(result.Config.UnknownAttrs()) if err != nil { return errors.Trace(err) } envConfig = updated } if err := instancecfg.FinishInstanceConfig(icfg, envConfig); err != nil { return err } maybeSetBridge(icfg) return FinishBootstrap(ctx, client, env, result.Instance, icfg, opts) } return result, selectedSeries, finalize, nil }
// bootstrapImageMetadata returns the image metadata to use for bootstrapping // the given environment. If the environment provider does not make use of // simplestreams, no metadata will be returned. // // If a bootstrap image ID is specified, image metadata will be synthesised // using that image ID, and the architecture and series specified by the // initiator. In addition, the custom image metadata that is saved into the // state database will have the synthesised image metadata added to it. func bootstrapImageMetadata( environ environs.Environ, availableTools coretools.List, bootstrapImageId string, customImageMetadata *[]*imagemetadata.ImageMetadata, ) ([]*imagemetadata.ImageMetadata, error) { hasRegion, ok := environ.(simplestreams.HasRegion) if !ok { if bootstrapImageId != "" { // We only support specifying image IDs for providers // that use simplestreams for now. return nil, errors.NotSupportedf( "specifying bootstrap image for %q provider", environ.Config().Type(), ) } // No region, no metadata. return nil, nil } region, err := hasRegion.Region() if err != nil { return nil, errors.Trace(err) } if bootstrapImageId != "" { arches := availableTools.Arches() if len(arches) != 1 { return nil, errors.NotValidf("multiple architectures with bootstrap image") } allSeries := availableTools.AllSeries() if len(allSeries) != 1 { return nil, errors.NotValidf("multiple series with bootstrap image") } seriesVersion, err := series.SeriesVersion(allSeries[0]) if err != nil { return nil, errors.Trace(err) } // The returned metadata does not have information about the // storage or virtualisation type. Any provider that wants to // filter on those properties should allow for empty values. meta := &imagemetadata.ImageMetadata{ Id: bootstrapImageId, Arch: arches[0], Version: seriesVersion, RegionName: region.Region, Endpoint: region.Endpoint, Stream: environ.Config().ImageStream(), } *customImageMetadata = append(*customImageMetadata, meta) return []*imagemetadata.ImageMetadata{meta}, nil } // For providers that support making use of simplestreams // image metadata, search public image metadata. We need // to pass this onto Bootstrap for selecting images. sources, err := environs.ImageMetadataSources(environ) if err != nil { return nil, errors.Trace(err) } imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ CloudSpec: region, Series: availableTools.AllSeries(), Arches: availableTools.Arches(), Stream: environ.Config().ImageStream(), }) logger.Debugf("constraints for image metadata lookup %v", imageConstraint) // Get image metadata from all data sources. // Since order of data source matters, order of image metadata matters too. Append is important here. var publicImageMetadata []*imagemetadata.ImageMetadata for _, source := range sources { sourceMetadata, _, err := imagemetadata.Fetch([]simplestreams.DataSource{source}, imageConstraint) if err != nil { logger.Debugf("ignoring image metadata in %s: %v", source.Description(), err) // Just keep looking... continue } logger.Debugf("found %d image metadata in %s", len(sourceMetadata), source.Description()) publicImageMetadata = append(publicImageMetadata, sourceMetadata...) } logger.Debugf("found %d image metadata from all image data sources", len(publicImageMetadata)) if len(publicImageMetadata) == 0 { return nil, errors.New("no image metadata found") } return publicImageMetadata, nil }
func (s *supportedSeriesSuite) TestSeriesVersionEmpty(c *gc.C) { setSeriesTestData() _, err := series.SeriesVersion("") c.Assert(err, gc.ErrorMatches, `.*unknown version for series: "".*`) }
// BootstrapInstance creates a new instance with the series and architecture // of its choice, constrained to those of the available tools, and // returns the instance result, series, and a function that // must be called to finalize the bootstrap process by transferring // the tools and installing the initial Juju controller. // This method is called by Bootstrap above, which implements environs.Bootstrap, but // is also exported so that providers can manipulate the started instance. func BootstrapInstance(ctx environs.BootstrapContext, env environs.Environ, args environs.BootstrapParams, ) (_ *environs.StartInstanceResult, selectedSeries string, _ environs.BootstrapFinalizer, err error) { // TODO make safe in the case of racing Bootstraps // If two Bootstraps are called concurrently, there's // no way to make sure that only one succeeds. // First thing, ensure we have tools otherwise there's no point. if args.BootstrapSeries != "" { selectedSeries = args.BootstrapSeries } else { selectedSeries = config.PreferredSeries(env.Config()) } availableTools, err := args.AvailableTools.Match(coretools.Filter{ Series: selectedSeries, }) if err != nil { return nil, "", nil, err } // Filter image metadata to the selected series. var imageMetadata []*imagemetadata.ImageMetadata seriesVersion, err := series.SeriesVersion(selectedSeries) if err != nil { return nil, "", nil, errors.Trace(err) } for _, m := range args.ImageMetadata { if m.Version != seriesVersion { continue } imageMetadata = append(imageMetadata, m) } // Get the bootstrap SSH client. Do this early, so we know // not to bother with any of the below if we can't finish the job. client := ssh.DefaultClient if client == nil { // This should never happen: if we don't have OpenSSH, then // go.crypto/ssh should be used with an auto-generated key. return nil, "", nil, fmt.Errorf("no SSH client available") } publicKey, err := simplestreams.UserPublicSigningKey() if err != nil { return nil, "", nil, err } instanceConfig, err := instancecfg.NewBootstrapInstanceConfig( args.BootstrapConstraints, args.ModelConstraints, selectedSeries, publicKey, ) if err != nil { return nil, "", nil, err } instanceConfig.EnableOSRefreshUpdate = env.Config().EnableOSRefreshUpdate() instanceConfig.EnableOSUpgrade = env.Config().EnableOSUpgrade() instanceConfig.Tags = instancecfg.InstanceTags(env.Config(), instanceConfig.Jobs) maybeSetBridge := func(icfg *instancecfg.InstanceConfig) { // If we need to override the default bridge name, do it now. When // args.ContainerBridgeName is empty, the default names for LXC // (lxcbr0) and KVM (virbr0) will be used. if args.ContainerBridgeName != "" { logger.Debugf("using %q as network bridge for all container types", args.ContainerBridgeName) if icfg.AgentEnvironment == nil { icfg.AgentEnvironment = make(map[string]string) } icfg.AgentEnvironment[agent.LxcBridge] = args.ContainerBridgeName } } maybeSetBridge(instanceConfig) fmt.Fprintln(ctx.GetStderr(), "Launching instance") instanceStatus := func(settableStatus status.Status, info string, data map[string]interface{}) error { fmt.Fprintf(ctx.GetStderr(), "%s \r", info) return nil } result, err := env.StartInstance(environs.StartInstanceParams{ Constraints: args.BootstrapConstraints, Tools: availableTools, InstanceConfig: instanceConfig, Placement: args.Placement, ImageMetadata: imageMetadata, StatusCallback: instanceStatus, }) if err != nil { return nil, "", nil, errors.Annotate(err, "cannot start bootstrap instance") } fmt.Fprintf(ctx.GetStderr(), " - %s\n", result.Instance.Id()) finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig) error { icfg.InstanceId = result.Instance.Id() icfg.HardwareCharacteristics = result.Hardware envConfig := env.Config() if result.Config != nil { updated, err := envConfig.Apply(result.Config.UnknownAttrs()) if err != nil { return errors.Trace(err) } envConfig = updated } if err := instancecfg.FinishInstanceConfig(icfg, envConfig); err != nil { return err } maybeSetBridge(icfg) return FinishBootstrap(ctx, client, env, result.Instance, icfg) } return result, selectedSeries, finalize, nil }
// bootstrapImageMetadata returns the image metadata to use for bootstrapping // the given environment. If the environment provider does not make use of // simplestreams, no metadata will be returned. // // If a bootstrap image ID is specified, image metadat will be synthesised // using that image ID, and the architecture and series specified by the // initiator. In addition, the custom image metadat that is saved into the // state database will have the synthesised image metadata added to it. func bootstrapImageMetadata( environ environs.Environ, availableTools coretools.List, bootstrapImageId string, customImageMetadata *[]*imagemetadata.ImageMetadata, ) ([]*imagemetadata.ImageMetadata, error) { hasRegion, ok := environ.(simplestreams.HasRegion) if !ok { if bootstrapImageId != "" { // We only support specifying image IDs for providers // that use simplestreams for now. return nil, errors.NotSupportedf( "specifying bootstrap image for %q provider", environ.Config().Type(), ) } // No region, no metadata. return nil, nil } region, err := hasRegion.Region() if err != nil { return nil, errors.Trace(err) } if bootstrapImageId != "" { arches := availableTools.Arches() if len(arches) != 1 { return nil, errors.NotValidf("multiple architectures with bootstrap image") } allSeries := availableTools.AllSeries() if len(allSeries) != 1 { return nil, errors.NotValidf("multiple series with bootstrap image") } seriesVersion, err := series.SeriesVersion(allSeries[0]) if err != nil { return nil, errors.Trace(err) } // The returned metadata does not have information about the // storage or virtualisation type. Any provider that wants to // filter on those properties should allow for empty values. meta := &imagemetadata.ImageMetadata{ Id: bootstrapImageId, Arch: arches[0], Version: seriesVersion, RegionName: region.Region, Endpoint: region.Endpoint, Stream: environ.Config().ImageStream(), } *customImageMetadata = append(*customImageMetadata, meta) return []*imagemetadata.ImageMetadata{meta}, nil } // For providers that support making use of simplestreams // image metadata, search public image metadata. We need // to pass this onto Bootstrap for selecting images. sources, err := environs.ImageMetadataSources(environ) if err != nil { return nil, errors.Trace(err) } imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{ CloudSpec: region, Series: availableTools.AllSeries(), Arches: availableTools.Arches(), Stream: environ.Config().ImageStream(), }) publicImageMetadata, _, err := imagemetadata.Fetch(sources, imageConstraint) if err != nil { return nil, errors.Annotate(err, "searching image metadata") } return publicImageMetadata, nil }
) error { called = true c.Check(objType, gc.Equals, "ImageMetadata") c.Check(id, gc.Equals, "") c.Check(request, gc.Equals, "UpdateFromPublishedImages") return errors.New(msg) }) client := imagemetadata.NewClient(apiCaller) err := client.UpdateFromPublishedImages() c.Assert(errors.Cause(err), gc.ErrorMatches, msg) c.Assert(called, jc.IsTrue) } var versionFromSeries = func(s string) string { // For testing purposes only, there will not be an error :D v, _ := series.SeriesVersion(s) return v } func (s *imagemetadataSuite) TestDelete(c *gc.C) { imageId := "tst12345" called := false apiCaller := testing.APICallerFunc( func(objType string, version int, id, request string, a, result interface{}, ) error { called = true c.Check(objType, gc.Equals, "ImageMetadata")
func (s *imagemetadataSuite) TestList(c *gc.C) { // setup data for test imageId := "imageid" stream := "stream" region := "region" // This is used by filters to search function testSeries := "trusty" version, err := series.SeriesVersion(testSeries) c.Assert(err, jc.ErrorIsNil) arch := "arch" virtType := "virt-type" rootStorageType := "root-storage-type" rootStorageSize := uint64(1024) source := "source" called := false apiCaller := testing.APICallerFunc( func(objType string, version int, id, request string, a, result interface{}, ) error { called = true c.Check(objType, gc.Equals, "ImageMetadata") c.Check(id, gc.Equals, "") c.Check(request, gc.Equals, "List") args, ok := a.(params.ImageMetadataFilter) c.Assert(ok, jc.IsTrue) if results, k := result.(*params.ListCloudImageMetadataResult); k { instances := []params.CloudImageMetadata{ params.CloudImageMetadata{ ImageId: imageId, Stream: args.Stream, Region: args.Region, Version: versionFromSeries(args.Series[0]), Series: args.Series[0], Arch: args.Arches[0], VirtType: args.VirtType, RootStorageType: args.RootStorageType, RootStorageSize: &rootStorageSize, Source: source, }, } results.Result = instances } return nil }) client := imagemetadata.NewClient(apiCaller) found, err := client.List( stream, region, []string{testSeries}, []string{arch}, virtType, rootStorageType, ) c.Check(err, jc.ErrorIsNil) c.Assert(called, jc.IsTrue) expected := []params.CloudImageMetadata{ params.CloudImageMetadata{ ImageId: imageId, Stream: stream, Region: region, Version: version, Series: testSeries, Arch: arch, VirtType: virtType, RootStorageType: rootStorageType, RootStorageSize: &rootStorageSize, Source: source, }, } c.Assert(found, jc.DeepEquals, expected) }