From 38fc2fedd4ebc6d387994df931b1f1c269fd2030 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 8 Feb 2019 20:28:49 +0000 Subject: [PATCH] adm/release-new: Add --coreos-url to override machine-os-content For RHCOS we have two things: - The "bootimage" (AMI, qcow2, PXE env) - The "oscontainer", now represented as `machine-os-content` in the payload For initial OpenShift releases (e.g. of the installer) ideally these are the same (i.e. we don't upgrade OS on boot). This PR aims to support injecting both data into the release payload. More information on the "bootimage" and its consumption by the installer as well as the Machine API Operator: https://github.com/openshift/installer/issues/987 More information on `machine-os-content`: https://github.com/openshift/machine-config-operator/issues/183 --- pkg/oc/cli/admin/release/coreos.go | 105 +++++++++++++++++++++++++++++ pkg/oc/cli/admin/release/new.go | 39 +++++++++++ 2 files changed, 144 insertions(+) create mode 100644 pkg/oc/cli/admin/release/coreos.go diff --git a/pkg/oc/cli/admin/release/coreos.go b/pkg/oc/cli/admin/release/coreos.go new file mode 100644 index 000000000000..1c138f3da15d --- /dev/null +++ b/pkg/oc/cli/admin/release/coreos.go @@ -0,0 +1,105 @@ +package release + +// This package parses the HTTP API effectively +// created by https://github.com/coreos/coreos-assembler + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + + "github.com/pkg/errors" +) + +// BuildMeta is a partial deserialization of the `meta.json` generated +// by coreos-assembler for a build. +type BuildMeta struct { + AMIs []struct { + HVM string `json:"hvm"` + Name string `json:"name"` + } `json:"amis"` + BuildID string `json:"buildid"` + Images struct { + QEMU struct { + Path string `json:"path"` + SHA256 string `json:"sha256"` + } `json:"qemu"` + } `json:"images"` + OSTreeVersion string `json:"ostree-version"` + OSContainer struct { + Digest string `json:"digest"` + Image string `json:"image"` + } `json:"oscontainer"` +} + +// httpGetAll downloads a URL and gives you a byte array. +func httpGetAll(ctx context.Context, url string) ([]byte, error) { + var body []byte + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return body, errors.Wrap(err, "failed to build request") + } + + client := &http.Client{} + resp, err := client.Do(req.WithContext(ctx)) + if err != nil { + return body, errors.Wrapf(err, "failed to fetch %s", url) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return body, errors.Errorf("fetching %s status %s", url, resp.Status) + } + + body, err = ioutil.ReadAll(resp.Body) + if err != nil { + return body, errors.Wrap(err, "failed to read HTTP response") + } + + return body, nil +} + +// getLatestBuildVersion returns the latest CoreOS build version number +func getLatestBuildVersion(ctx context.Context, baseURL string) (string, error) { + var builds struct { + Builds []string `json:"builds"` + } + buildsBuf, err := httpGetAll(ctx, baseURL + "/builds.json") + if err != nil { + return "", err + } + if err := json.Unmarshal(buildsBuf, &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 +} + +// GetLatest returns the CoreOS build with target version. If version +// is the empty string, the latest will be used. +func GetCoreOSBuild(ctx context.Context, baseURL string, version string) (*BuildMeta, error) { + var err error + if version == "" { + version, err = getLatestBuildVersion(ctx, baseURL) + if err != nil { + return nil, err + } + } + buildUrl := fmt.Sprintf("%s/%s/meta.json", baseURL, version) + buildStr, err := httpGetAll(ctx, buildUrl) + if err != nil { + return nil, err + } + + var build BuildMeta + if err := json.Unmarshal(buildStr, &build); err != nil { + return nil, errors.Wrap(err, "failed to parse HTTP response") + } + return &build, nil +} diff --git a/pkg/oc/cli/admin/release/new.go b/pkg/oc/cli/admin/release/new.go index 0da12e993d77..629ef5121947 100644 --- a/pkg/oc/cli/admin/release/new.go +++ b/pkg/oc/cli/admin/release/new.go @@ -4,6 +4,7 @@ import ( "archive/tar" "bufio" "bytes" + "context" "compress/gzip" "encoding/json" "fmt" @@ -39,6 +40,12 @@ import ( "github.com/openshift/origin/pkg/oc/cli/image/extract" ) +const ( + // coreOSBootImageLabel contains JSON metadata from coreos-assembler. + // In the future it may be used by the installer. + coreOSBootImageLabel = "io.openshift.release.coreos-boot-image" +) + func NewNewOptions(streams genericclioptions.IOStreams) *NewOptions { return &NewOptions{ IOStreams: streams, @@ -102,6 +109,8 @@ func NewRelease(f kcmdutil.Factory, parentName string, streams genericclioptions flags.StringVar(&o.FromDirectory, "from-dir", o.FromDirectory, "Use this directory as the source for the release payload.") flags.StringVar(&o.FromReleaseImage, "from-release", o.FromReleaseImage, "Use an existing release image as input.") flags.StringVar(&o.ReferenceMode, "reference-mode", o.ReferenceMode, "By default, the image reference from an image stream points to the public registry for the stream and the image digest. Pass 'source' to build references to the originating image.") + flags.StringVar(&o.CoreOSURL, "coreos-url", o.CoreOSURL, "URL for CoreOS release server") + flags.StringVar(&o.CoreOSVersion, "coreos-version", o.CoreOSVersion, "Choose this CoreOS version instead of picking latest in the stream") // properties of the release flags.StringVar(&o.Name, "name", o.Name, "The name of the release. Will default to the current time.") @@ -148,6 +157,8 @@ type NewOptions struct { FromImageStream string Namespace string ReferenceMode string + CoreOSURL string + CoreOSVersion string Exclude []string AlwaysInclude []string @@ -538,6 +549,32 @@ func (o *NewOptions) Run() error { is.Annotations = make(map[string]string) } + if o.CoreOSURL != "" { + coreosBuild, err := GetCoreOSBuild(context.TODO(), o.CoreOSURL, o.CoreOSVersion) + if err != nil { + return err + } + fmt.Fprintf(o.Out, "Using CoreOS build %s\n", coreosBuild.BuildID) + digestedImage := fmt.Sprintf("%s@%s", coreosBuild.OSContainer.Image, coreosBuild.OSContainer.Digest) + if digestedImage == "@" { + return fmt.Errorf("No oscontainer in CoreOS build") + } + + // Hardcoded, this name was chosen in one of the machine-config-operator PRs + // and added to the release payload by Clayton. + o.Mappings = append(o.Mappings, Mapping{Source: "machine-os-content", + Destination: digestedImage}) + + // And inject the full build metadata - primarily useful for + // "bootimages" i.e. AMIs/qcow2/etc. + serializedBuild, err := json.Marshal(coreosBuild) + if err != nil { + return err + } + // This is written as a label in the final image + is.Annotations[coreOSBootImageLabel] = string(serializedBuild) + } + // update any custom mappings and then sort the spec tags for _, m := range o.Mappings { if exclude.Has(m.Source) { @@ -930,6 +967,8 @@ func (o *NewOptions) write(r io.Reader, is *imageapi.ImageStream, now time.Time) if len(dgst) > 0 { config.Config.Labels["io.openshift.release.base-image-digest"] = dgst.String() } + config.Config.Labels[coreOSBootImageLabel] = is.Annotations[coreOSBootImageLabel] + return nil }