Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor RHCOS fetch API, support fetching qemu from partial config #856

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions pkg/asset/installconfig/libvirt/libvirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,19 @@ 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{
IfName: defaultNetworkIfName,
IPRange: *defaultNetworkIPRange,
},
DefaultMachinePlatform: &libvirt.MachinePool{
Image: qcowImage,
Image: qcowImage,
},
URI: uri,
}, nil
Expand Down
8 changes: 6 additions & 2 deletions pkg/asset/machines/master.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/asset/machines/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
13 changes: 3 additions & 10 deletions pkg/rhcos/ami.go
Original file line number Diff line number Diff line change
@@ -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)
}
43 changes: 28 additions & 15 deletions pkg/rhcos/builds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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 {
Expand Down
15 changes: 6 additions & 9 deletions pkg/rhcos/qemu.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package rhcos

import (
"context"
"os"
"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")
func QEMU(build Build) string {
qcowImage, ok := os.LookupEnv("OPENSHIFT_INSTALL_LIBVIRT_IMAGE")
if ok {
return qcowImage
}

return fmt.Sprintf("%s/%s/%s/%s", baseURL, channel, meta.OSTreeVersion, meta.Images.QEMU.Path), nil
return fmt.Sprintf("%s/%s/%s/%s", build.BaseURL, build.Channel, build.Meta.OSTreeVersion, build.Meta.Images.QEMU.Path)
}
2 changes: 1 addition & 1 deletion pkg/tfvars/libvirt/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Member

@wking wking Dec 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The log message should describe the resource being fetched, but "URL" is just how we're fetching it. If you don't like "OS image", how about "QEMU image"? Or "QCOW2" (although I don't know if we actually require this to be QCOW2 or if libvirt can handle other formats too)?


// FIXME: Use os.UserCacheDir() once we bump to Go 1.11
// baseCacheDir, err := os.UserCacheDir()
Expand Down
1 change: 1 addition & 0 deletions pkg/tfvars/libvirt/libvirt.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
22 changes: 19 additions & 3 deletions pkg/tfvars/tfvars.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand All @@ -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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too late (and the in-master AMI fetch above is also too late). We need these resolved by the time we generate the machine(-set) values we set up for the cluster API. #792 tries to straighten out dependencies to make this work.

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 {
Expand Down
5 changes: 5 additions & 0 deletions pkg/types/libvirt/machinepool.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
cgwalters marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MachinePool is for user-supplied input (although we supply defaults when the user doesn't care). This string doesn't seem like an actionable input.

}

// Set sets the values from `required` to `a`.
Expand All @@ -30,4 +32,7 @@ func (l *MachinePool) Set(required *MachinePool) {
if required.Image != "" {
l.Image = required.Image
}
if required.ImageDescription != "" {
l.ImageDescription = required.ImageDescription
}
}