From 47adb4e1eab6a89fc466a0dbfbe33aaf1b369f86 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 10 Dec 2018 13:16:29 -0500 Subject: [PATCH 1/2] Refactor RHCOS fetch API, support fetching qemu from partial config The *main* thing I'm really fixing here is that I have an installer wrapper script: https://github.com/cgwalters/homegit/blob/master/bin/okdinst And I noticed recently that I kept fetching old RHCOS versions; that's of course because the image URL was hardcoded in my install config. What I really want is to fetch that dynamically. So this PR changes the TF rendering code to do an on-demand fetch if the image isn't filled out in the libvirt config. Now further, as I was arguing in this PR: https://github.com/openshift/installer/pull/710#issuecomment-440486784 The result of an install run from installer git master is currently a function of the 3-tuple: (installer, RHCOS, release payload) I'd actually like to print out all 3 clearly in the logs; this is a first step. Today as far as I can see we do separate fetches in AWS for master, bootstrap and worker, which is a bit broken. Need to move that to a higher level, and this will help with that. --- pkg/asset/installconfig/libvirt/libvirt.go | 5 ++- pkg/asset/machines/master.go | 8 +++- pkg/asset/machines/worker.go | 6 ++- pkg/rhcos/ami.go | 13 ++----- pkg/rhcos/builds.go | 43 ++++++++++++++-------- pkg/rhcos/qemu.go | 12 +----- pkg/tfvars/libvirt/cache.go | 2 +- pkg/tfvars/libvirt/libvirt.go | 1 + pkg/tfvars/tfvars.go | 22 +++++++++-- pkg/types/libvirt/machinepool.go | 5 +++ 10 files changed, 73 insertions(+), 44 deletions(-) diff --git a/pkg/asset/installconfig/libvirt/libvirt.go b/pkg/asset/installconfig/libvirt/libvirt.go index a9e86acace3..1b83f726921 100644 --- a/pkg/asset/installconfig/libvirt/libvirt.go +++ b/pkg/asset/installconfig/libvirt/libvirt.go @@ -38,10 +38,11 @@ func Platform() (*libvirt.Platform, error) { return nil, err } - qcowImage, err := rhcos.QEMU(context.TODO(), rhcos.DefaultChannel) + rhcosBuild, err := rhcos.FetchBuild(context.TODO(), rhcos.DefaultChannel) if err != nil { return nil, errors.Wrap(err, "failed to fetch QEMU image URL") } + qcowImage := rhcos.QEMU(rhcosBuild) return &libvirt.Platform{ Network: libvirt.Network{ @@ -49,7 +50,7 @@ func Platform() (*libvirt.Platform, error) { IPRange: *defaultNetworkIPRange, }, DefaultMachinePlatform: &libvirt.MachinePool{ - Image: qcowImage, + Image: qcowImage, }, URI: uri, }, nil diff --git a/pkg/asset/machines/master.go b/pkg/asset/machines/master.go index 21515aad227..4739b8a9e6a 100644 --- a/pkg/asset/machines/master.go +++ b/pkg/asset/machines/master.go @@ -70,9 +70,13 @@ func (m *Master) Generate(dependencies asset.Parents) error { if mpool.AMIID == "" { ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) defer cancel() - ami, err := rhcos.AMI(ctx, rhcos.DefaultChannel, ic.Platform.AWS.Region) + rhcosBuild, err := rhcos.FetchBuild(ctx, rhcos.DefaultChannel) if err != nil { - return errors.Wrap(err, "failed to determine default AMI") + return err + } + ami, err := rhcos.AMI(rhcosBuild, ic.Platform.AWS.Region) + if err != nil { + return err } mpool.AMIID = ami } diff --git a/pkg/asset/machines/worker.go b/pkg/asset/machines/worker.go index 89e71014ece..63224de100d 100644 --- a/pkg/asset/machines/worker.go +++ b/pkg/asset/machines/worker.go @@ -84,7 +84,11 @@ func (w *Worker) Generate(dependencies asset.Parents) error { if mpool.AMIID == "" { ctx, cancel := context.WithTimeout(context.TODO(), 60*time.Second) defer cancel() - ami, err := rhcos.AMI(ctx, rhcos.DefaultChannel, ic.Platform.AWS.Region) + rhcosBuild, err := rhcos.FetchBuild(ctx, rhcos.DefaultChannel) + if err != nil { + return err + } + ami, err := rhcos.AMI(rhcosBuild, ic.Platform.AWS.Region) if err != nil { return errors.Wrap(err, "failed to determine default AMI") } diff --git a/pkg/rhcos/ami.go b/pkg/rhcos/ami.go index 2726e4c6fcd..825dc975ab6 100644 --- a/pkg/rhcos/ami.go +++ b/pkg/rhcos/ami.go @@ -1,23 +1,16 @@ package rhcos import ( - "context" - "github.com/pkg/errors" ) // AMI fetches the HVM AMI ID of the latest Red Hat CoreOS release. -func AMI(ctx context.Context, channel, region string) (string, error) { - meta, err := fetchLatestMetadata(ctx, channel) - if err != nil { - return "", errors.Wrap(err, "failed to fetch RHCOS metadata") - } - - for _, ami := range meta.AMIs { +func AMI(build Build, region string) (string, error) { + for _, ami := range build.Meta.AMIs { if ami.Name == region { return ami.HVM, nil } } - return "", errors.Errorf("no RHCOS AMIs found in %s", region) + return "", errors.Errorf("RHCOS build %s does not have AMIs in region %s", build.Meta.OSTreeVersion, region) } diff --git a/pkg/rhcos/builds.go b/pkg/rhcos/builds.go index 015b50ef615..5a4f89ecfc9 100644 --- a/pkg/rhcos/builds.go +++ b/pkg/rhcos/builds.go @@ -19,10 +19,19 @@ var ( // empty string means the first one in the build list (latest) will be used buildName = "" - baseURL = "https://releases-rhcos.svc.ci.openshift.org/storage/releases" + defaultBaseURL = "https://releases-rhcos.svc.ci.openshift.org/storage/releases" ) -type metadata struct { +// Represents context around a fetched build +type Build struct { + BaseURL string + Channel string + Meta Metadata +} + +// Metadata is a parse of meta.json as generated by coreos-assembler which is +// a single build. +type Metadata struct { AMIs []struct { HVM string `json:"hvm"` Name string `json:"name"` @@ -36,49 +45,53 @@ type metadata struct { OSTreeVersion string `json:"ostree-version"` } -func fetchLatestMetadata(ctx context.Context, channel string) (metadata, error) { +func FetchBuild(ctx context.Context, channel string) (Build, error) { build := buildName var err error if build == "" { - build, err = fetchLatestBuild(ctx, channel) + build, err = fetchLatestBuildVersion(ctx, channel) if err != nil { - return metadata{}, errors.Wrap(err, "failed to fetch latest build") + return Build{}, errors.Wrap(err, "failed to fetch latest build") } } - url := fmt.Sprintf("%s/%s/%s/meta.json", baseURL, channel, build) + url := fmt.Sprintf("%s/%s/%s/meta.json", defaultBaseURL, channel, build) logrus.Debugf("Fetching RHCOS metadata from %q", url) req, err := http.NewRequest("GET", url, nil) if err != nil { - return metadata{}, errors.Wrap(err, "failed to build request") + return Build{}, errors.Wrap(err, "failed to build request") } client := &http.Client{} resp, err := client.Do(req.WithContext(ctx)) if err != nil { - return metadata{}, errors.Wrapf(err, "failed to fetch metadata for build %s", build) + return Build{}, errors.Wrapf(err, "failed to fetch metadata for build %s", build) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return metadata{}, errors.Errorf("incorrect HTTP response (%s)", resp.Status) + return Build{}, errors.Errorf("incorrect HTTP response (%s)", resp.Status) } body, err := ioutil.ReadAll(resp.Body) if err != nil { - return metadata{}, errors.Wrap(err, "failed to read HTTP response") + return Build{}, errors.Wrap(err, "failed to read HTTP response") } - var meta metadata + var meta Metadata if err := json.Unmarshal(body, &meta); err != nil { - return meta, errors.Wrap(err, "failed to parse HTTP response") + return Build{}, errors.Wrap(err, "failed to parse HTTP response") } - return meta, nil + return Build{ + BaseURL: defaultBaseURL, + Channel: channel, + Meta: meta, + }, nil } -func fetchLatestBuild(ctx context.Context, channel string) (string, error) { - url := fmt.Sprintf("%s/%s/builds.json", baseURL, channel) +func fetchLatestBuildVersion(ctx context.Context, channel string) (string, error) { + url := fmt.Sprintf("%s/%s/builds.json", defaultBaseURL, channel) logrus.Debugf("Fetching RHCOS builds from %q", url) req, err := http.NewRequest("GET", url, nil) if err != nil { diff --git a/pkg/rhcos/qemu.go b/pkg/rhcos/qemu.go index 8fd30de8c91..e88fc070a1f 100644 --- a/pkg/rhcos/qemu.go +++ b/pkg/rhcos/qemu.go @@ -1,18 +1,10 @@ package rhcos import ( - "context" "fmt" - - "github.com/pkg/errors" ) // QEMU fetches the URL of the latest Red Hat CoreOS release. -func QEMU(ctx context.Context, channel string) (string, error) { - meta, err := fetchLatestMetadata(ctx, channel) - if err != nil { - return "", errors.Wrap(err, "failed to fetch RHCOS metadata") - } - - return fmt.Sprintf("%s/%s/%s/%s", baseURL, channel, meta.OSTreeVersion, meta.Images.QEMU.Path), nil +func QEMU(build Build) string { + return fmt.Sprintf("%s/%s/%s/%s", build.BaseURL, build.Channel, build.Meta.OSTreeVersion, build.Meta.Images.QEMU.Path) } diff --git a/pkg/tfvars/libvirt/cache.go b/pkg/tfvars/libvirt/cache.go index b985b448099..093f1838da3 100644 --- a/pkg/tfvars/libvirt/cache.go +++ b/pkg/tfvars/libvirt/cache.go @@ -28,7 +28,7 @@ func (libvirt *Libvirt) UseCachedImage() (err error) { return nil } - logrus.Infof("Fetching OS image: %s", filepath.Base(libvirt.Image)) + logrus.Infof("Fetching qemu URL: %s", filepath.Base(libvirt.Image)) // FIXME: Use os.UserCacheDir() once we bump to Go 1.11 // baseCacheDir, err := os.UserCacheDir() diff --git a/pkg/tfvars/libvirt/libvirt.go b/pkg/tfvars/libvirt/libvirt.go index 8643daf077d..cc1a3aafcae 100644 --- a/pkg/tfvars/libvirt/libvirt.go +++ b/pkg/tfvars/libvirt/libvirt.go @@ -11,6 +11,7 @@ import ( type Libvirt struct { URI string `json:"libvirt_uri,omitempty"` Image string `json:"os_image,omitempty"` + ImageDesc string `json:"os_image_desc,omitempty"` Network `json:",inline"` MasterIPs []string `json:"libvirt_master_ips,omitempty"` BootstrapIP string `json:"libvirt_bootstrap_ip,omitempty"` diff --git a/pkg/tfvars/tfvars.go b/pkg/tfvars/tfvars.go index 0f159d5ee87..c76807f238a 100644 --- a/pkg/tfvars/tfvars.go +++ b/pkg/tfvars/tfvars.go @@ -76,9 +76,13 @@ func TFVars(cfg *types.InstallConfig, bootstrapIgn, masterIgn string) ([]byte, e if cfg.Platform.AWS != nil { ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) defer cancel() - ami, err := rhcos.AMI(ctx, rhcos.DefaultChannel, cfg.Platform.AWS.Region) + rhcosBuild, err := rhcos.FetchBuild(ctx, rhcos.DefaultChannel) if err != nil { - return nil, errors.Wrap(err, "failed to determine default AMI") + return nil, err + } + ami, err := rhcos.AMI(rhcosBuild, cfg.Platform.AWS.Region) + if err != nil { + return nil, err } config.AWS = aws.AWS{ @@ -97,13 +101,25 @@ func TFVars(cfg *types.InstallConfig, bootstrapIgn, masterIgn string) ([]byte, e for i, ip := range cfg.Platform.Libvirt.MasterIPs { masterIPs[i] = ip.String() } + + // Do an on-demand fetch if the image isn't specified (e.g. user + // is reusing an install config) + qemuImage := cfg.Platform.Libvirt.DefaultMachinePlatform.Image + if qemuImage == "" { + rhcosBuild, err := rhcos.FetchBuild(context.TODO(), rhcos.DefaultChannel) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch QEMU image URL") + } + qemuImage = rhcos.QEMU(rhcosBuild) + } + config.Libvirt = libvirt.Libvirt{ URI: cfg.Platform.Libvirt.URI, Network: libvirt.Network{ IfName: cfg.Platform.Libvirt.Network.IfName, IPRange: cfg.Platform.Libvirt.Network.IPRange.String(), }, - Image: cfg.Platform.Libvirt.DefaultMachinePlatform.Image, + Image: qemuImage, MasterIPs: masterIPs, } if err := config.Libvirt.TFVars(config.Masters); err != nil { diff --git a/pkg/types/libvirt/machinepool.go b/pkg/types/libvirt/machinepool.go index 00dadb5e609..dd882d11b12 100644 --- a/pkg/types/libvirt/machinepool.go +++ b/pkg/types/libvirt/machinepool.go @@ -13,6 +13,8 @@ type MachinePool struct { // Image is the URL to the OS image. // E.g. "http://aos-ostree.rhev-ci-vms.eng.rdu2.redhat.com/rhcos/images/cloud/latest/rhcos-qemu.qcow2.gz" Image string `json:"image"` + // Metadata string describing the image + ImageDescription string `json:"imageDescription,omitempty"` } // Set sets the values from `required` to `a`. @@ -30,4 +32,7 @@ func (l *MachinePool) Set(required *MachinePool) { if required.Image != "" { l.Image = required.Image } + if required.ImageDescription != "" { + l.ImageDescription = required.ImageDescription + } } From b547e648c1362f403c50c6e7fa18e824ffcf4620 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Mon, 7 Jan 2019 14:47:52 +0000 Subject: [PATCH 2/2] rhcos/qemu: Readd environment var OPENSHIFT_INSTALL_LIBVIRT_IMAGE This is primarily targeted for local development/testing. --- pkg/rhcos/qemu.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg/rhcos/qemu.go b/pkg/rhcos/qemu.go index e88fc070a1f..8e8f2095f90 100644 --- a/pkg/rhcos/qemu.go +++ b/pkg/rhcos/qemu.go @@ -1,10 +1,15 @@ package rhcos import ( + "os" "fmt" ) // QEMU fetches the URL of the latest Red Hat CoreOS release. func QEMU(build Build) string { + qcowImage, ok := os.LookupEnv("OPENSHIFT_INSTALL_LIBVIRT_IMAGE") + if ok { + return qcowImage + } return fmt.Sprintf("%s/%s/%s/%s", build.BaseURL, build.Channel, build.Meta.OSTreeVersion, build.Meta.Images.QEMU.Path) }