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

WIP: add --coreos-url to oc adm release new #21998

Closed
wants to merge 1 commit 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
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 {
Copy link
Member

Choose a reason for hiding this comment

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

I'd just drop this and include only the keys we need for now. Not that we're planning to break the schema, though there's no point in deserializing it if we're not using it.

Speaking of, I think we discussed adding schema versioning to the JSON output. Probably would be good to do this before teaching the origin about it?

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
Copy link
Member

Choose a reason for hiding this comment

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

GetCoreOSBuild

// 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")
Copy link
Member

Choose a reason for hiding this comment

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

Why not do an explicit e.g. if coreosBuild.OSContainer.Image == "" || coreosBuild.OSContainer.Digest == "" { ... }? (This also catches the case where just one of the two is somehow missing.)

}

// 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)
Copy link
Member

Choose a reason for hiding this comment

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

Ahh OK, I see now why you wanted to also deserialize the qcow2s. Though... isn't there a way to have this data be a proper configmap instead of JSON-in-a-label? We were discussing this in openshift/machine-config-operator#183 right? (And including the pkglist, etc...).

Anyway, totally fine doing it this way for now too!

}

// 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