Skip to content

Commit

Permalink
WIP: add --coreos-url to oc adm release new
Browse files Browse the repository at this point in the history
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:
openshift/installer#987

More information on `machine-os-content`:
openshift/machine-config-operator#183
  • Loading branch information
cgwalters committed Feb 9, 2019
1 parent 1ae3c41 commit 166036e
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 0 deletions.
105 changes: 105 additions & 0 deletions pkg/oc/cli/admin/release/coreos.go
Original file line number Diff line number Diff line change
@@ -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
}
39 changes: 39 additions & 0 deletions pkg/oc/cli/admin/release/new.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"archive/tar"
"bufio"
"bytes"
"context"
"compress/gzip"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.")
Expand Down Expand Up @@ -148,6 +157,8 @@ type NewOptions struct {
FromImageStream string
Namespace string
ReferenceMode string
CoreOSURL string
CoreOSVersion string

Exclude []string
AlwaysInclude []string
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
}

Expand Down

0 comments on commit 166036e

Please sign in to comment.