-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
rhcos: implement image discovery for new pipeline #554
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,86 +2,22 @@ package rhcos | |
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/ec2" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
const ( | ||
// DefaultChannel is the default RHCOS channel for the cluster. | ||
DefaultChannel = "tested" | ||
) | ||
|
||
// AMI calculates a Red Hat CoreOS AMI. | ||
func AMI(ctx context.Context, channel, region string) (ami string, err error) { | ||
if channel != DefaultChannel { | ||
return "", errors.Errorf("channel %q is not yet supported", channel) | ||
} | ||
|
||
ssn := session.Must(session.NewSessionWithOptions(session.Options{ | ||
SharedConfigState: session.SharedConfigEnable, | ||
Config: aws.Config{ | ||
Region: aws.String(region), | ||
}, | ||
})) | ||
|
||
svc := ec2.New(ssn) | ||
|
||
result, err := svc.DescribeImagesWithContext(ctx, &ec2.DescribeImagesInput{ | ||
Filters: []*ec2.Filter{ | ||
{ | ||
Name: aws.String("name"), | ||
Values: aws.StringSlice([]string{"redhat-coreos-*"}), | ||
}, | ||
{ | ||
Name: aws.String("architecture"), | ||
Values: aws.StringSlice([]string{"x86_64"}), | ||
}, | ||
{ | ||
Name: aws.String("virtualization-type"), | ||
Values: aws.StringSlice([]string{"hvm"}), | ||
}, | ||
{ | ||
Name: aws.String("image-type"), | ||
Values: aws.StringSlice([]string{"machine"}), | ||
}, | ||
{ | ||
Name: aws.String("owner-id"), | ||
Values: aws.StringSlice([]string{"531415883065"}), | ||
}, | ||
{ | ||
Name: aws.String("state"), | ||
Values: aws.StringSlice([]string{"available"}), | ||
}, | ||
}, | ||
}) | ||
// 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 describe AMIs") | ||
return "", errors.Wrap(err, "failed to fetch RHCOS metadata") | ||
} | ||
|
||
var image *ec2.Image | ||
var created time.Time | ||
for _, nextImage := range result.Images { | ||
if nextImage.ImageId == nil || nextImage.CreationDate == nil { | ||
continue | ||
for _, ami := range meta.AMIs { | ||
if ami.Name == region { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to land this before the AMIs are being pushed to other regions? $ BUILD=$(curl -sL https://releases-rhcos.svc.ci.openshift.org/storage/releases/maipo/builds.json | jq -r '.builds[0]')
$ echo "${BUILD}"
47.38
$ curl -sL "https://releases-rhcos.svc.ci.openshift.org/storage/releases/maipo/${BUILD}/meta.json" | jq .amis
[
{
"name": "us-east-1",
"hvm": "ami-03afbcc8b72560ac8"
}
] I'd rather stick to scraping AWS until we get that, because scraping AWS has a chance at working in other regions if folks can copy the AMI over to the target region. That would allow us to use the other regions we're trying to use for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can add an environment variable for the AMI. We already have the override for the QEMU image. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm ok with that, although see this comment about not having AMI/image knobs and assuming that there's a default AMI/image that is (1) available and (2) healthy enough to pivot. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're close to landing the multi-region again although this is going to slow our pipeline notably. There are two things we need to do; first on the RHCOS side, implement tags, and only upload to all the regions for tags (e.g. once a day at most?). Second though we need to do openshift/os#289 or possibly teach the installer to take a set of override RPMs and apply them via Ignition? Then I think we can slow the cadence of "promoted" builds, and anyone who wants to test bleeding edge OS work can use libvirt. |
||
return ami.HVM, nil | ||
} | ||
nextCreated, err := time.Parse(time.RFC3339, *nextImage.CreationDate) | ||
if err != nil { | ||
return "", errors.Wrap(err, "failed to parse AMIs CreationDate to time.RFC3339") | ||
} | ||
|
||
if image == nil || nextCreated.After(created) { | ||
image = nextImage | ||
created = nextCreated | ||
} | ||
} | ||
|
||
if image == nil { | ||
return "", errors.Errorf("no RHCOS AMIs found in %s", region) | ||
} | ||
|
||
return *image.ImageId, nil | ||
return "", errors.Errorf("no RHCOS AMIs found in %s", region) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package rhcos | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
|
||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
const ( | ||
// DefaultChannel is the default RHCOS channel for the cluster. | ||
DefaultChannel = "maipo" | ||
|
||
baseURL = "https://releases-rhcos.svc.ci.openshift.org/storage/releases" | ||
) | ||
|
||
type metadata struct { | ||
AMIs []struct { | ||
HVM string `json:"hvm"` | ||
Name string `json:"name"` | ||
} `json:"amis"` | ||
Images struct { | ||
QEMU struct { | ||
Path string `json:"path"` | ||
SHA256 string `json:"sha256"` | ||
} `json:"qemu"` | ||
} `json:"images"` | ||
OSTreeVersion string `json:"ostree-version"` | ||
} | ||
|
||
func fetchLatestMetadata(ctx context.Context, channel string) (metadata, error) { | ||
build, err := fetchLatestBuild(ctx, channel) | ||
if err != nil { | ||
return metadata{}, errors.Wrap(err, "failed to fetch latest build") | ||
} | ||
|
||
url := fmt.Sprintf("%s/%s/%s/meta.json", baseURL, 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") | ||
} | ||
|
||
client := &http.Client{} | ||
resp, err := client.Do(req.WithContext(ctx)) | ||
if err != nil { | ||
return metadata{}, errors.Wrap(err, "failed to fetch metadata") | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
return metadata{}, 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") | ||
} | ||
|
||
var meta metadata | ||
if err := json.Unmarshal(body, &meta); err != nil { | ||
return meta, errors.Wrap(err, "failed to parse HTTP response") | ||
} | ||
|
||
return meta, nil | ||
} | ||
|
||
func fetchLatestBuild(ctx context.Context, channel string) (string, error) { | ||
url := fmt.Sprintf("%s/%s/builds.json", baseURL, channel) | ||
logrus.Debugf("Fetching RHCOS builds from %q", url) | ||
req, err := http.NewRequest("GET", url, nil) | ||
if err != nil { | ||
return "", errors.Wrap(err, "failed to build request") | ||
} | ||
|
||
client := &http.Client{} | ||
resp, err := client.Do(req.WithContext(ctx)) | ||
if err != nil { | ||
return "", errors.Wrap(err, "failed to fetch builds") | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode != http.StatusOK { | ||
return "", errors.Errorf("incorrect HTTP response (%s)", resp.Status) | ||
} | ||
|
||
body, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
return "", errors.Wrap(err, "failed to read HTTP response") | ||
} | ||
|
||
var builds struct { | ||
Builds []string `json:"builds"` | ||
} | ||
if err := json.Unmarshal(body, &builds); err != nil { | ||
return "", errors.Wrap(err, "failed to parse HTTP response") | ||
} | ||
|
||
if len(builds.Builds) == 0 { | ||
return "", errors.Errorf("no builds found") | ||
} | ||
|
||
return builds.Builds[0], nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
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 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sort of problem goes away with #556, where all dependencies and defaults are only rendered as needed 🎉