From 8cc545c834f33bad219cd4a17d1ab7d0e2d8c9d3 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Tue, 16 Apr 2024 18:16:09 +0200 Subject: Adding upstream version 0.4.3. Signed-off-by: Daniel Baumann --- .github/ISSUE_TEMPLATE/release-checklist.md | 19 ++ .github/dependabot.yml | 13 + .github/workflows/go.yml | 51 ++++ .github/workflows/require-release-note.yml | 27 ++ LICENSE | 201 ++++++++++++++ Makefile | 18 ++ README.md | 11 + arch/arch.go | 48 ++++ arch/arch_test.go | 19 ++ docs/development.md | 7 + docs/release-notes.md | 101 +++++++ example/example.go | 76 ++++++ fedoracoreos/fcos.go | 60 +++++ fedoracoreos/fcos_test.go | 10 + fedoracoreos/internals/fcosinternals.go | 33 +++ go.mod | 11 + go.sum | 10 + release/fixtures/fcos-release.json | 255 ++++++++++++++++++ release/fixtures/fcos-releases.json | 269 +++++++++++++++++++ release/release.go | 151 +++++++++++ release/release_test.go | 56 ++++ release/rhcos/rhcos.go | 14 + release/translate.go | 289 ++++++++++++++++++++ stream/artifact_utils.go | 95 +++++++ stream/fixtures/fcos-stream.json | 397 ++++++++++++++++++++++++++++ stream/rhcos/rhcos.go | 18 ++ stream/stream.go | 116 ++++++++ stream/stream_test.go | 73 +++++ stream/stream_utils.go | 94 +++++++ tag_release.sh | 29 ++ 30 files changed, 2571 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/release-checklist.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/go.yml create mode 100644 .github/workflows/require-release-note.yml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 arch/arch.go create mode 100644 arch/arch_test.go create mode 100644 docs/development.md create mode 100644 docs/release-notes.md create mode 100644 example/example.go create mode 100644 fedoracoreos/fcos.go create mode 100644 fedoracoreos/fcos_test.go create mode 100644 fedoracoreos/internals/fcosinternals.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 release/fixtures/fcos-release.json create mode 100644 release/fixtures/fcos-releases.json create mode 100644 release/release.go create mode 100644 release/release_test.go create mode 100644 release/rhcos/rhcos.go create mode 100644 release/translate.go create mode 100644 stream/artifact_utils.go create mode 100644 stream/fixtures/fcos-stream.json create mode 100644 stream/rhcos/rhcos.go create mode 100644 stream/stream.go create mode 100644 stream/stream_test.go create mode 100644 stream/stream_utils.go create mode 100755 tag_release.sh diff --git a/.github/ISSUE_TEMPLATE/release-checklist.md b/.github/ISSUE_TEMPLATE/release-checklist.md new file mode 100644 index 0000000..323c724 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release-checklist.md @@ -0,0 +1,19 @@ +--- +# Template generated by https://github.com/coreos/repo-templates; do not edit downstream +--- + +Release checklist: + +Tagging: + - [ ] Write release notes in `docs/release-notes.md`. Get them reviewed and merged + - [ ] If doing a branched release, also include a PR to merge the `docs/release-notes.md` changes into main + - [ ] Ensure your local copy is up to date with the upstream main branch (`git@github.com:coreos/stream-metadata-go.git`) + - [ ] Ensure your working directory is clean (`git clean -fdx`) + - [ ] Ensure you can sign commits and any yubikeys/smartcards are plugged in + - [ ] Run `./tag_release.sh ` + - [ ] Push that tag to GitHub + +GitHub release: + - [ ] Find the new tag in the [GitHub tag list](https://github.com/coreos/stream-metadata-go/tags) and click the triple dots menu, and create a draft release for it. + - [ ] Copy and paste the release notes from `docs/release-notes.md` + - [ ] Publish the release diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f8ba261 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Maintained in https://github.com/coreos/repo-templates +# Do not edit downstream. + +version: 2 +updates: + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + open-pull-requests-limit: 10 + labels: + - dependency + - skip-notes diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..2d9e845 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,51 @@ +# Maintained in https://github.com/coreos/repo-templates +# Do not edit downstream. + +name: Go +on: + push: + branches: [main] + pull_request: + branches: [main] +permissions: + contents: read + +# don't waste job slots on superseded code +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Test + strategy: + matrix: + go-version: [1.18.x, 1.19.x, 1.20.x] + os: [ubuntu-latest] + include: + - go-version: 1.20.x + os: macos-latest + - go-version: 1.20.x + os: windows-latest + runs-on: ${{ matrix.os }} + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - name: Check out repository + uses: actions/checkout@v3 + - name: Check modules + run: go mod verify + - name: Build + shell: bash + run: make + - name: Test + shell: bash + run: make test + - name: Run linter + uses: golangci/golangci-lint-action@v3 + if: runner.os == 'Linux' + with: + version: v1.52.2 + args: -E=gofmt --timeout=30m0s diff --git a/.github/workflows/require-release-note.yml b/.github/workflows/require-release-note.yml new file mode 100644 index 0000000..611fb5a --- /dev/null +++ b/.github/workflows/require-release-note.yml @@ -0,0 +1,27 @@ +# Maintained in https://github.com/coreos/repo-templates +# Do not edit downstream. + +name: Release notes + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened, labeled, unlabeled] + +permissions: + contents: read + +concurrency: + group: release-note-${{ github.ref }} + cancel-in-progress: true + +jobs: + require-notes: + name: Require release note + runs-on: ubuntu-latest + steps: + - name: Require release-notes.md update + uses: coreos/actions-lib/require-file-change@main + with: + path: docs/release-notes.md + override-label: skip-notes diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c7af386 --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +PKGS := arch release stream stream/rhcos release/rhcos fedoracoreos + +BUILD_PKGS = $(foreach pkg,$(PKGS),build-$(pkg)) +TEST_PKGS = $(foreach pkg,$(PKGS),test-$(pkg)) + +build: $(BUILD_PKGS) +.PHONY: build + +$(BUILD_PKGS): build-%: + cd $* && go build +.PHONY: $(BUILD_PKGS) + +test: $(TEST_PKGS) +.PHONY: test + +$(TEST_PKGS): test-%: + cd $* && go test +.PHONY: $(TEST_PKGS) diff --git a/README.md b/README.md new file mode 100644 index 0000000..11c1466 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Go library for parsing Fedora CoreOS streams + +See the [Fedora CoreOS documentation](https://docs.fedoraproject.org/en-US/fedora-coreos/getting-started/) +for basic information about streams. + +This is a Go library which exposes API to decode streams into Go structs, +as well as a convenience API to find the URL for a given stream. + +# Example usage + +See [example code here](example/example.go). diff --git a/arch/arch.go b/arch/arch.go new file mode 100644 index 0000000..d4a3e6b --- /dev/null +++ b/arch/arch.go @@ -0,0 +1,48 @@ +// package arch contains mappings between the Golang architecture and +// the RPM architecture used by Fedora CoreOS and derivatives. +package arch + +import "runtime" + +type mapping struct { + rpmArch string + goArch string +} + +// If an architecture isn't defined here, we assume it's +// pass through. +var translations = []mapping{ + { + rpmArch: "x86_64", + goArch: "amd64", + }, + { + rpmArch: "aarch64", + goArch: "arm64", + }, +} + +// CurrentRpmArch returns the current architecture in RPM terms. +func CurrentRpmArch() string { + return RpmArch(runtime.GOARCH) +} + +// RpmArch translates a Go architecture to RPM. +func RpmArch(goarch string) string { + for _, m := range translations { + if m.goArch == goarch { + return m.rpmArch + } + } + return goarch +} + +// GoArch translates an RPM architecture to Go. +func GoArch(rpmarch string) string { + for _, m := range translations { + if m.rpmArch == rpmarch { + return m.goArch + } + } + return rpmarch +} diff --git a/arch/arch_test.go b/arch/arch_test.go new file mode 100644 index 0000000..8913e41 --- /dev/null +++ b/arch/arch_test.go @@ -0,0 +1,19 @@ +// package arch contains mappings between the Golang architecture and +// the RPM architecture used by Fedora CoreOS and derivatives. +package arch + +import ( + "runtime" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMapping(t *testing.T) { + // Validate bidirectional mapping for current architecture + assert.Equal(t, GoArch(CurrentRpmArch()), runtime.GOARCH) + + assert.Equal(t, GoArch("x86_64"), "amd64") + assert.Equal(t, GoArch("aarch64"), "arm64") + assert.Equal(t, GoArch("ppc64le"), "ppc64le") +} diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..fdcf51d --- /dev/null +++ b/docs/development.md @@ -0,0 +1,7 @@ +# Development + +## Release process + +Releases can be performed by [creating a new release ticket][new-release-ticket] and following the steps in the checklist there. + +[new-release-ticket]: https://github.com/coreos/stream-metadata-go/issues/new?template=release-checklist.md diff --git a/docs/release-notes.md b/docs/release-notes.md new file mode 100644 index 0000000..bbd41c1 --- /dev/null +++ b/docs/release-notes.md @@ -0,0 +1,101 @@ +# Release notes + +## Upcoming stream-metadata-go 0.4.4 (unreleased) + +Changes: + + + +## stream-metadata-go 0.4.3 (2023-06-28) + +Changes: + +- Add support for Hyper-V images + + +## stream-metadata-go 0.4.2 (2023-06-01) + +Changes: + +- Drop support for Go 1.17 +- Add release notes doc + + +## stream-metadata-go 0.4.1 (2023-01-30) + +Changes: + +- Add support for Secure Execution + + +## stream-metadata-go 0.4.0 (2022-08-11) + +- Drop vendored dependencies +- Drop support for Go 1.15 and 1.16 +- Fix tests + + +## stream-metadata-go 0.3.0 (2022-04-15) + +- Switch KubeVirt image struct from `SingleImage` to new `ContainerImage` +- Add `ContainerImage.DigestRef` field so we can record both the recommended + pullspec and one that includes the digest + + +## stream-metadata-go 0.2.1 (2022-04-08) + +- Add support for VirtualBox images + + +## stream-metadata-go 0.2.0 (2022-04-05) + +- Rename `RegionObject`/`RegionImage` to `SingleObject`/`SingleImage`; + add compatibility aliases +- Drop `KubeVirtContainerDisk` types in favor of generic ones + + +## stream-metadata-go 0.1.8 (2022-03-15) + +- Add support for KubeVirt images + + +## stream-metadata-go 0.1.7 (2022-01-25) + +- Fix Windows build by dropping renameio dependency + + +## stream-metadata-go 0.1.6 (2021-11-29) + +- Add support for Nutanix images + + +## stream-metadata-go 0.1.5 (2021-11-02) + +- Add support for IBMCloud/PowerVS images +- Add `GcpImage.Release` field in stream metadata +- Drop `omitempty` from mandatory GcpImage fields + + +## stream-metadata-go 0.1.4 (2021-10-06) + +- Make `Artifact.Signature` `omitempty` + + +## stream-metadata-go 0.1.3 (2021-09-16) + +- Add `Metadata.Generator` field + + +## stream-metadata-go 0.1.2 (2021-09-15) + +- Add support for Alibaba Cloud (`aliyun`) cloud images + + +## stream-metadata-go 0.1.1 (2021-07-20) + +- Add support for Azure Stack images + + +## stream-metadata-go 0.1.0 (2021-04-26) + +- Initial release diff --git a/example/example.go b/example/example.go new file mode 100644 index 0000000..3a1b5ac --- /dev/null +++ b/example/example.go @@ -0,0 +1,76 @@ +// Package main contains an example use of this library; it +// prints the current Fedora CoreOS EC2(AWS) x86_64 AMI in the +// us-east-2 region. +package main + +import ( + "fmt" + "os" + + "github.com/coreos/stream-metadata-go/fedoracoreos" + "github.com/coreos/stream-metadata-go/stream" +) + +const ( + targetArch = "x86_64" + region = "us-east-2" +) + +func downloadISO(fcosstable stream.Stream) error { + iso, err := fcosstable.QueryDisk(targetArch, "metal", "iso") + if err != nil { + return err + } + + fmt.Printf("Downloading %s\n", iso.Location) + path, err := iso.Download(".") + if err != nil { + return fmt.Errorf("Failed to download %s: %w", iso.Location, err) + } + fmt.Printf("Downloaded %s\n", path) + + return nil +} + +func printAMI(fcosstable stream.Stream) error { + arch, ok := fcosstable.Architectures[targetArch] + if !ok { + return fmt.Errorf("No %s architecture in stream", targetArch) + } + awsimages := arch.Images.Aws + if awsimages == nil { + return fmt.Errorf("No %s AWS images in stream", targetArch) + } + var regionVal stream.SingleImage + if regionVal, ok = awsimages.Regions[region]; !ok { + return fmt.Errorf("No %s AWS images in region %s", targetArch, region) + } + fmt.Printf("%s\n", regionVal.Image) + + return nil +} + +func run() error { + if len(os.Args) != 2 { + return fmt.Errorf("usage: example aws-ami|download-iso") + } + arg := os.Args[1] + fcosstable, err := fedoracoreos.FetchStream(fedoracoreos.StreamStable) + if err != nil { + return err + } + if arg == "aws-ami" { + return printAMI(*fcosstable) + } else if arg == "download-iso" { + return downloadISO(*fcosstable) + } else { + return fmt.Errorf("invalid operation %s", arg) + } +} + +func main() { + if err := run(); err != nil { + fmt.Fprintf(os.Stderr, "%v\n", err) + os.Exit(1) + } +} diff --git a/fedoracoreos/fcos.go b/fedoracoreos/fcos.go new file mode 100644 index 0000000..9fc2b3e --- /dev/null +++ b/fedoracoreos/fcos.go @@ -0,0 +1,60 @@ +// Package fedoracoreos contains APIs defining well-known +// streams for Fedora CoreOS and a method to retrieve +// the URL for a stream endpoint. +package fedoracoreos + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + + "github.com/coreos/stream-metadata-go/fedoracoreos/internals" + "github.com/coreos/stream-metadata-go/stream" +) + +const ( + // StreamStable is the default stream + StreamStable = "stable" + // StreamTesting is what is intended to land in stable + StreamTesting = "testing" + // StreamNext usually tracks the next Fedora major version + StreamNext = "next" +) + +// GetStreamURL returns the URL for the given stream +func GetStreamURL(stream string) url.URL { + u := internals.GetBaseURL() + u.Path = fmt.Sprintf("streams/%s.json", stream) + return u +} + +func getStream(u url.URL) (*stream.Stream, error) { + resp, err := http.Get(u.String()) + if err != nil { + return nil, err + } + body, err := io.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + return nil, err + } + + var s stream.Stream + err = json.Unmarshal(body, &s) + if err != nil { + return nil, err + } + return &s, nil +} + +// FetchStream fetches and parses stream metadata for a stream +func FetchStream(streamName string) (*stream.Stream, error) { + u := GetStreamURL(streamName) + s, err := getStream(u) + if err != nil { + return nil, fmt.Errorf("failed to fetch stream %s: %w", streamName, err) + } + return s, nil +} diff --git a/fedoracoreos/fcos_test.go b/fedoracoreos/fcos_test.go new file mode 100644 index 0000000..64b13a3 --- /dev/null +++ b/fedoracoreos/fcos_test.go @@ -0,0 +1,10 @@ +package fedoracoreos + +import "testing" + +func TestGetStreamURL(t *testing.T) { + u := GetStreamURL(StreamStable) + if u.String() != "https://builds.coreos.fedoraproject.org/streams/stable.json" { + t.Fatalf("Invalid stream url") + } +} diff --git a/fedoracoreos/internals/fcosinternals.go b/fedoracoreos/internals/fcosinternals.go new file mode 100644 index 0000000..48b6add --- /dev/null +++ b/fedoracoreos/internals/fcosinternals.go @@ -0,0 +1,33 @@ +// Package internals contains functions for accessing +// the underlying "releases" and coreos-assembler builds +// backing streams. General user code should avoid +// this package and use streams. +package internals + +import ( + "fmt" + "net/url" +) + +// GetBaseURL returns the base URL +func GetBaseURL() url.URL { + return url.URL{ + Scheme: "https", + Host: "builds.coreos.fedoraproject.org", + } +} + +// GetReleaseIndexURL returns the URL for the release index of a given stream. +// Avoid this unless you have a specific need to test a specific release. +func GetReleaseIndexURL(stream string) url.URL { + u := GetBaseURL() + u.Path = fmt.Sprintf("prod/streams/%s/releases.json", stream) + return u +} + +// GetCosaBuild returns the coreos-assembler build URL +func GetCosaBuild(stream, buildID, arch string) url.URL { + u := GetBaseURL() + u.Path = fmt.Sprintf("prod/streams/%s/builds/%s/%s/", stream, buildID, arch) + return u +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4466d6e --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/coreos/stream-metadata-go + +go 1.18 + +require github.com/stretchr/testify v1.8.4 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..fa4b6e6 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/release/fixtures/fcos-release.json b/release/fixtures/fcos-release.json new file mode 100644 index 0000000..abaa98e --- /dev/null +++ b/release/fixtures/fcos-release.json @@ -0,0 +1,255 @@ +{ + "release": "33.20201201.3.0", + "stream": "stable", + "architectures": { + "x86_64": { + "media": { + "aliyun": { + "artifacts": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-aliyun.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-aliyun.x86_64.qcow2.xz.sig", + "sha256": "34f3bfd55b0c29804dc2dcace9a671c28b976b62e82e5c42651fd50cf5be4920" + } + } + } + }, + "aws": { + "artifacts": { + "vmdk.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-aws.x86_64.vmdk.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-aws.x86_64.vmdk.xz.sig", + "sha256": "5719f3ece03294ae64c0a8b0b1ddbb58f2d932a5923ece043f8446114f9e564a" + } + } + }, + "images": { + "us-east-1": { + "image": "ami-037a0ba6d14ca2e05" + }, + "me-south-1": { + "image": "ami-08830d7d8bf851531" + }, + "us-east-2": { + "image": "ami-091b0dbc05fe2dc06" + }, + "ca-central-1": { + "image": "ami-0d371d51950767885" + }, + "us-west-2": { + "image": "ami-071f6f91a3d8715d7" + }, + "us-west-1": { + "image": "ami-0f775df0ac05d0de6" + }, + "eu-west-2": { + "image": "ami-0b28ffc2766f88e01" + }, + "eu-west-3": { + "image": "ami-0fe2197196b3ca15c" + }, + "eu-south-1": { + "image": "ami-0da68c04abc8a25f2" + }, + "eu-central-1": { + "image": "ami-069b9a4f891c19440" + }, + "eu-west-1": { + "image": "ami-016a154c559c3b81a" + }, + "eu-north-1": { + "image": "ami-0a278fa12694ed1a5" + }, + "ap-east-1": { + "image": "ami-0cb22a422870c3b7f" + }, + "ap-south-1": { + "image": "ami-09de6e47b22138992" + }, + "ap-northeast-1": { + "image": "ami-0370b4b5b620d53b0" + }, + "af-south-1": { + "image": "ami-0bd959051ff340868" + }, + "ap-northeast-2": { + "image": "ami-09d33644806aa7550" + }, + "ap-southeast-2": { + "image": "ami-01e3071d351c82174" + }, + "ap-southeast-1": { + "image": "ami-059b22e3f95e447b8" + }, + "sa-east-1": { + "image": "ami-075fedcd8c24239bc" + } + } + }, + "azure": { + "artifacts": { + "vhd.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-azure.x86_64.vhd.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-azure.x86_64.vhd.xz.sig", + "sha256": "7d62c3d06b738268a65d3cd047a87e66b1a04c51b0088a11c87b7b27d224ece3" + } + } + } + }, + "digitalocean": { + "artifacts": { + "qcow2.gz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-digitalocean.x86_64.qcow2.gz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-digitalocean.x86_64.qcow2.gz.sig", + "sha256": "2b9c851edbc9227fec489cd736df2db11b96df76f82c135675d91103f62ad07b" + } + } + } + }, + "exoscale": { + "artifacts": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-exoscale.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-exoscale.x86_64.qcow2.xz.sig", + "sha256": "52342742531ad3b49b4f7727208aeed98ae917ff496cdbb32c08ba4c17c39da5" + } + } + } + }, + "gcp": { + "artifacts": { + "tar.gz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-gcp.x86_64.tar.gz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-gcp.x86_64.tar.gz.sig", + "sha256": "515b2e42b35a15d30ba770db2e896d161baea5801cc27874c8c8f06e43ce1de7" + } + } + }, + "image": { + "project": "fedora-coreos-cloud", + "family": "fedora-coreos-stable", + "name": "fedora-coreos-33-20201201-3-0-gcp-x86-64" + } + }, + "ibmcloud": { + "artifacts": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-ibmcloud.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-ibmcloud.x86_64.qcow2.xz.sig", + "sha256": "d74829e99bd29464a21e4879d0000b15e4afa944cfed0b5d8ae0b7d8931b6758" + } + } + } + }, + "metal": { + "artifacts": { + "raw.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-metal.x86_64.raw.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-metal.x86_64.raw.xz.sig", + "sha256": "2848b111a6917455686f38a3ce64d2321c33809b9cf796c5f6804b1c02d79d9d" + } + }, + "4k.raw.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-metal4k.x86_64.raw.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-metal4k.x86_64.raw.xz.sig", + "sha256": "45621b66ee3db98e3515d334f70e095ef58e03d20d77ae5b55668b421372b6ae" + } + }, + "iso": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live.x86_64.iso", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live.x86_64.iso.sig", + "sha256": "2f62f8f4813bbf584945052d54746cb83101b2c643705a33711ae26fce15fcba" + } + }, + "pxe": { + "kernel": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-kernel-x86_64", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-kernel-x86_64.sig", + "sha256": "d6bddcc1cc8f2642b11f38ca4602573211171814a53ab57c2655853008167407" + }, + "initramfs": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-initramfs.x86_64.img", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-initramfs.x86_64.img.sig", + "sha256": "0872233b1af67b728201b121434e8f990e339cebb85a19b4011b7a94fc83f8c3" + }, + "rootfs": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-rootfs.x86_64.img", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-rootfs.x86_64.img.sig", + "sha256": "64306ff0a5f20f4c897a2c20dbbe6764b300df85fa90f3b3f400dc8d80b11495" + } + } + } + }, + "openstack": { + "artifacts": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-openstack.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-openstack.x86_64.qcow2.xz.sig", + "sha256": "2be55c5aa1f53eb9a869826dacbab75706ee6bd59185b935ac9be546cc132a85" + } + } + } + }, + "kubevirt": { + "artifacts": { + "ociarchive": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-kubevirt.x86_64.ociarchive", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-kubevirt.x86_64.ociarchive.sig", + "sha256": "2be55c5aa1f53eb9a869826dacbab75706ee6bd59185b935ac9be546cc132a85" + } + } + }, + "image": { + "image": "quay.io/openshift-release-dev/rhcos@sha256:67a81539946ec0397196c145394553b8e0241acf27b14ae9de43bc56e167f773" + } + }, + "qemu": { + "artifacts": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-qemu.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-qemu.x86_64.qcow2.xz.sig", + "sha256": "a7e93e32665086d4a07a14dbe6c125177402f04603fc5bb575035028701afa5b" + } + } + } + }, + "vmware": { + "artifacts": { + "ova": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-vmware.x86_64.ova", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-vmware.x86_64.ova.sig", + "sha256": "f800c4cfd53ce2586e101d065eacb3c44082db936af777a71678d9c22d83934c" + } + } + } + }, + "vultr": { + "artifacts": { + "raw.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-vultr.x86_64.raw.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-vultr.x86_64.raw.xz.sig", + "sha256": "33ac69ed5fbf3ea6e0a6292894a07491c4b17987323986343ce49109f5f5df7e" + } + } + } + } + }, + "commit": "cad80088392fe43bd3cadf0481c3267f199afa7d9f83bc03937ffdbf5ebbc6da" + } + } +} diff --git a/release/fixtures/fcos-releases.json b/release/fixtures/fcos-releases.json new file mode 100644 index 0000000..7e4e5b6 --- /dev/null +++ b/release/fixtures/fcos-releases.json @@ -0,0 +1,269 @@ +{ + "note": "For use only by Fedora CoreOS internal tooling. All other applications should obtain release info from stream metadata endpoints.", + "releases": [ + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "113aa27efe1bbcf6324af7423f64ef7deb0acbf21b928faec84bf66a60a5c933" + } + ], + "version": "31.20200108.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200108.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "f480038412cba26ab010d2cd5a09ddec736204a6e9faa8370edaa943cf33c932" + } + ], + "version": "31.20200113.3.1", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200113.3.1/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "093f7da6ffa161ae1648a05be9c55f758258ab97b55c628bea5259f6ac6e370e" + } + ], + "version": "31.20200118.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200118.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "2ba7bb9678d86fc469f1920a03c270d7057e0b93d06fcd1a437f1f79bdc26d83" + } + ], + "version": "31.20200127.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200127.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "4ea6beed22d0adc4599452de85820f6e157ac1750e688d062bfedc765b193505" + } + ], + "version": "31.20200210.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200210.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "3477082298eb5e2213433415c78f0919a991b40e62726b8fde434d244c1ec1b6" + } + ], + "version": "31.20200223.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200223.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "436592e6eb93e899bebab8dbd17514c85be683390ef8bbce8c6d96069ce4c543" + } + ], + "version": "31.20200310.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200310.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "9b322b9cead87ebf80881b75560e481b6e5a26966814b01bf0c12b61fa363708" + } + ], + "version": "31.20200323.3.2", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200323.3.2/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "89e17cc21b6aa3bea8959d1e6957fda157168d57ba6805d8a36142184edc2901" + } + ], + "version": "31.20200407.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200407.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "b3fc3a3e8513d7e424d0ced1e2517484cb766d238951f2fdec3da2fed3522efb" + } + ], + "version": "31.20200420.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200420.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "01f074cc6cd88d8d2b43f821da692f2367c101eb4377802cb35092bde0ef02f7" + } + ], + "version": "31.20200505.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200505.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "967b7b8d624e6d10ff51c2e81ef198fae966c567ac2e9b479771c693d0987949" + } + ], + "version": "31.20200517.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/31.20200517.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "b51037798e93e5aae5123633fb596c80ddf30302b5110b0581900dbc5b2f0d24" + } + ], + "version": "32.20200601.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200601.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "abb4b978999bd689ff36114e99c737be16026bc154a4428d4c84a340758c5b01" + } + ], + "version": "32.20200615.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200615.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "6df95bdb2fe2d36e091d4d18e3844fa84ce4b80ea3bd0947db5d7a286ff41890" + } + ], + "version": "32.20200629.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200629.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "a3b08ee51b1d950afd9d0d73f32d5424ad52c7703a6b5830e0dc11c3a682d869" + } + ], + "version": "32.20200715.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200715.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "2579b41aa614c3a40b9e24ff0b9dd288f99222dc3ed3a527ef0d8e8667196ff5" + } + ], + "version": "32.20200726.3.1", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200726.3.1/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "902c88022e834fb7998140734fae01b75f78b80f63364e9c5309d62d9a260b7c" + } + ], + "version": "32.20200809.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200809.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "321d73d8865b2899510461cf16c4c2adb60e70031898c8d89e10eed2ac0e438a" + } + ], + "version": "32.20200824.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200824.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "b53de8b03134c5e6b683b5ea471888e9e1b193781794f01b9ed5865b57f35d57" + } + ], + "version": "32.20200907.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200907.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "5090d96f4faf090c90c5f4ebf6c3cdab458acc89f2ba08d1ff704744a143b7e6" + } + ], + "version": "32.20200923.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200923.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "64bb377ae7e6949c26cfe819f3f0bd517596d461e437f2f6e9f1f3c24376fd30" + } + ], + "version": "32.20201004.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20201004.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "a6f3e91ad6e0b8e132769c1694ed9b747a339c04e4e6256fce3afd81c85e1f1f" + } + ], + "version": "32.20201018.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20201018.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "318de830c2f30a97333cd43aa1d500a46ccfedcb2de70a04d0c48228944346da" + } + ], + "version": "32.20201104.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20201104.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "cad80088392fe43bd3cadf0481c3267f199afa7d9f83bc03937ffdbf5ebbc6da" + } + ], + "version": "33.20201201.3.0", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/release.json" + }, + { + "commits": [ + { + "architecture": "x86_64", + "checksum": "a64854cbcec13e1c3b3ccbfd3802e377e23c0c136d384c32736addf77e0e2a03" + } + ], + "version": "33.20201214.3.1", + "metadata": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201214.3.1/release.json" + } + ], + "metadata": { + "last-modified": "2021-01-05T10:15:52Z" + }, + "stream": "stable" +} diff --git a/release/release.go b/release/release.go new file mode 100644 index 0000000..5864260 --- /dev/null +++ b/release/release.go @@ -0,0 +1,151 @@ +// Package release contains APIs for interacting with a +// particular "release". Avoid this unless you are sure +// you need it. It's expected that CoreOS users interact +// with streams instead. +package release + +import ( + relrhcos "github.com/coreos/stream-metadata-go/release/rhcos" +) + +// Index models the release index: +// https://github.com/coreos/fedora-coreos-tracker/tree/main/metadata/release-index +type Index struct { + Note string `json:"note"` // used to note to users not to consume the release metadata index + Releases []IndexRelease `json:"releases"` + Metadata Metadata `json:"metadata"` + Stream string `json:"stream"` +} + +// IndexRelease is a "release pointer" from a release index +type IndexRelease struct { + Commits []IndexReleaseCommit `json:"commits"` + Version string `json:"version"` + MetadataURL string `json:"metadata"` +} + +// IndexReleaseCommit describes an ostree commit plus architecture +type IndexReleaseCommit struct { + Architecture string `json:"architecture"` + Checksum string `json:"checksum"` +} + +// Release contains details from release.json +type Release struct { + Release string `json:"release"` + Stream string `json:"stream"` + Metadata Metadata `json:"metadata"` + Architectures map[string]Arch `json:"architectures"` +} + +// Metadata is common metadata that contains last-modified +type Metadata struct { + LastModified string `json:"last-modified"` +} + +// Arch release details +type Arch struct { + Commit string `json:"commit"` + Media Media `json:"media"` + RHELCoreOSExtensions *relrhcos.Extensions `json:"rhel-coreos-extensions,omitempty"` +} + +// Media contains release details for various platforms +type Media struct { + Aliyun *PlatformAliyun `json:"aliyun"` + Aws *PlatformAws `json:"aws"` + Azure *PlatformBase `json:"azure"` + AzureStack *PlatformBase `json:"azurestack"` + Digitalocean *PlatformBase `json:"digitalocean"` + Exoscale *PlatformBase `json:"exoscale"` + Gcp *PlatformGcp `json:"gcp"` + HyperV *PlatformBase `json:"hyperv"` + Ibmcloud *PlatformIBMCloud `json:"ibmcloud"` + KubeVirt *PlatformKubeVirt `json:"kubevirt"` + Metal *PlatformBase `json:"metal"` + Nutanix *PlatformBase `json:"nutanix"` + Openstack *PlatformBase `json:"openstack"` + PowerVS *PlatformIBMCloud `json:"powervs"` + Qemu *PlatformBase `json:"qemu"` + QemuSecex *PlatformBase `json:"qemu-secex"` + VirtualBox *PlatformBase `json:"virtualbox"` + Vmware *PlatformBase `json:"vmware"` + Vultr *PlatformBase `json:"vultr"` +} + +// PlatformBase with no cloud images +type PlatformBase struct { + Artifacts map[string]ImageFormat `json:"artifacts"` +} + +// PlatformAliyun contains Aliyun image information +type PlatformAliyun struct { + PlatformBase + Images map[string]CloudImage `json:"images"` +} + +// PlatformAws contains AWS image information +type PlatformAws struct { + PlatformBase + Images map[string]CloudImage `json:"images"` +} + +// PlatformGcp GCP image detail +type PlatformGcp struct { + PlatformBase + Image *GcpImage `json:"image"` +} + +// PlatformIBMCloud IBMCloud/PowerVS image detail +type PlatformIBMCloud struct { + PlatformBase + Images map[string]IBMCloudImage `json:"images"` +} + +// PlatformKubeVirt containerDisk metadata +type PlatformKubeVirt struct { + PlatformBase + Image *ContainerImage `json:"image"` +} + +// ImageFormat contains all artifacts for a single OS image +type ImageFormat struct { + Disk *Artifact `json:"disk,omitempty"` + Kernel *Artifact `json:"kernel,omitempty"` + Initramfs *Artifact `json:"initramfs,omitempty"` + Rootfs *Artifact `json:"rootfs,omitempty"` +} + +// Artifact represents one image file, plus its metadata +type Artifact struct { + Location string `json:"location"` + Signature string `json:"signature"` + Sha256 string `json:"sha256"` + UncompressedSha256 string `json:"uncompressed-sha256,omitempty"` +} + +// CloudImage generic image detail +type CloudImage struct { + Image string `json:"image"` +} + +// ContainerImage represents a tagged container image +type ContainerImage struct { + // Preferred way to reference the image, which might be by tag or digest + Image string `json:"image"` + DigestRef string `json:"digest-ref"` +} + +// GcpImage represents a GCP cloud image +type GcpImage struct { + Project string `json:"project"` + Family string `json:"family,omitempty"` + Name string `json:"name"` +} + +// IBMCloudImage represents an IBMCloud/PowerVS cloud object - which is an ova image for PowerVS and a qcow for IBMCloud in the cloud object storage bucket +type IBMCloudImage struct { + Object string `json:"object"` + Bucket string `json:"bucket"` + Url string `json:"url"` +} diff --git a/release/release_test.go b/release/release_test.go new file mode 100644 index 0000000..218ec64 --- /dev/null +++ b/release/release_test.go @@ -0,0 +1,56 @@ +package release + +import ( + "encoding/json" + "os" + "testing" + "time" + + "github.com/coreos/stream-metadata-go/stream" + "github.com/stretchr/testify/assert" +) + +const ( + usEast2Ami = "ami-091b0dbc05fe2dc06" +) + +func TestParseFCR(t *testing.T) { + d, err := os.ReadFile("fixtures/fcos-release.json") + assert.Nil(t, err) + release := Release{} + err = json.Unmarshal(d, &release) + assert.Nil(t, err) + assert.Equal(t, release.Stream, "stable") + assert.Equal(t, release.Architectures["x86_64"].Media.Aws.Images["us-east-2"].Image, usEast2Ami) +} + +func TestParseFCRIndex(t *testing.T) { + d, err := os.ReadFile("fixtures/fcos-releases.json") + assert.Nil(t, err) + idx := Index{} + err = json.Unmarshal(d, &idx) + assert.Nil(t, err) + assert.Equal(t, idx.Stream, "stable") + release := idx.Releases[0] + assert.Equal(t, release.Version, "31.20200108.3.0") + assert.Equal(t, release.Commits[0].Architecture, "x86_64") + assert.Equal(t, release.Commits[0].Checksum, "113aa27efe1bbcf6324af7423f64ef7deb0acbf21b928faec84bf66a60a5c933") +} + +func TestTranslate(t *testing.T) { + d, err := os.ReadFile("fixtures/fcos-release.json") + assert.Nil(t, err) + rel := Release{} + err = json.Unmarshal(d, &rel) + assert.Nil(t, err) + arches := rel.ToStreamArchitectures() + st := stream.Stream{ + Stream: rel.Stream, + Metadata: stream.Metadata{LastModified: time.Now().UTC().Format(time.RFC3339)}, + Architectures: arches, + } + assert.Equal(t, st.Architectures["x86_64"].Images.Aws.Regions["us-east-2"].Image, usEast2Ami) + + // KubeVirt + assert.Equal(t, st.Architectures["x86_64"].Images.KubeVirt.Image, "quay.io/openshift-release-dev/rhcos@sha256:67a81539946ec0397196c145394553b8e0241acf27b14ae9de43bc56e167f773") +} diff --git a/release/rhcos/rhcos.go b/release/rhcos/rhcos.go new file mode 100644 index 0000000..aeae2c8 --- /dev/null +++ b/release/rhcos/rhcos.go @@ -0,0 +1,14 @@ +package rhcos + +// Extensions is data specific to Red Hat Enterprise Linux CoreOS +type Extensions struct { + AzureDisk *AzureDisk `json:"azure-disk,omitempty"` +} + +// AzureDisk represents an Azure cloud image. +type AzureDisk struct { + // URL to an image already stored in Azure infrastructure + // that can be copied into an image gallery. Avoid creating VMs directly + // from this URL as that may lead to performance limitations. + URL string `json:"url,omitempty"` +} diff --git a/release/translate.go b/release/translate.go new file mode 100644 index 0000000..d96c869 --- /dev/null +++ b/release/translate.go @@ -0,0 +1,289 @@ +package release + +import ( + "github.com/coreos/stream-metadata-go/stream" + "github.com/coreos/stream-metadata-go/stream/rhcos" +) + +func mapArtifact(ra *Artifact) *stream.Artifact { + if ra == nil { + return nil + } + return &stream.Artifact{ + Location: ra.Location, + Signature: ra.Signature, + Sha256: ra.Sha256, + UncompressedSha256: ra.UncompressedSha256, + } +} + +func mapFormats(m map[string]ImageFormat) map[string]stream.ImageFormat { + r := make(map[string]stream.ImageFormat) + for k, v := range m { + r[k] = stream.ImageFormat{ + Disk: mapArtifact(v.Disk), + Kernel: mapArtifact(v.Kernel), + Initramfs: mapArtifact(v.Initramfs), + Rootfs: mapArtifact(v.Rootfs), + } + } + return r +} + +// Convert a release architecture to a stream architecture +func (releaseArch *Arch) toStreamArch(rel *Release) stream.Arch { + artifacts := make(map[string]stream.PlatformArtifacts) + cloudImages := stream.Images{} + var rhcosExt *rhcos.Extensions + relRHCOSExt := releaseArch.RHELCoreOSExtensions + if relRHCOSExt != nil { + rhcosExt = &rhcos.Extensions{} + } + + if releaseArch.Media.Aliyun != nil { + artifacts["aliyun"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Aliyun.Artifacts), + } + aliyunImages := stream.ReplicatedImage{ + Regions: make(map[string]stream.SingleImage), + } + if releaseArch.Media.Aliyun.Images != nil { + for region, image := range releaseArch.Media.Aliyun.Images { + si := stream.SingleImage{Release: rel.Release, Image: image.Image} + aliyunImages.Regions[region] = si + + } + cloudImages.Aliyun = &aliyunImages + } + } + + if releaseArch.Media.Aws != nil { + artifacts["aws"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Aws.Artifacts), + } + awsAmis := stream.ReplicatedImage{ + Regions: make(map[string]stream.SingleImage), + } + if releaseArch.Media.Aws.Images != nil { + for region, ami := range releaseArch.Media.Aws.Images { + si := stream.SingleImage{Release: rel.Release, Image: ami.Image} + awsAmis.Regions[region] = si + + } + cloudImages.Aws = &awsAmis + } + } + + if releaseArch.Media.Azure != nil { + artifacts["azure"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Azure.Artifacts), + } + + if relRHCOSExt != nil { + az := relRHCOSExt.AzureDisk + if az != nil { + rhcosExt.AzureDisk = &rhcos.AzureDisk{ + Release: rel.Release, + URL: az.URL, + } + } + } + // In the future this is where we'd also add FCOS Marketplace data. + // See https://github.com/coreos/stream-metadata-go/issues/13 + } + + if releaseArch.Media.AzureStack != nil { + artifacts["azurestack"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.AzureStack.Artifacts), + } + } + + if releaseArch.Media.Digitalocean != nil { + artifacts["digitalocean"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Digitalocean.Artifacts), + } + + /* We're producing artifacts but they're not yet available + in DigitalOcean as distribution images. + digitalOceanImage := stream.CloudImage{Image: fmt.Sprintf("fedora-coreos-%s", Stream)} + cloudImages.Digitalocean = &digitalOceanImage + */ + } + + if releaseArch.Media.Exoscale != nil { + artifacts["exoscale"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Exoscale.Artifacts), + } + } + + if releaseArch.Media.Gcp != nil { + artifacts["gcp"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Gcp.Artifacts), + } + + if releaseArch.Media.Gcp.Image != nil { + cloudImages.Gcp = &stream.GcpImage{ + Release: rel.Release, + Name: releaseArch.Media.Gcp.Image.Name, + Family: releaseArch.Media.Gcp.Image.Family, + Project: releaseArch.Media.Gcp.Image.Project, + } + } + } + + if releaseArch.Media.HyperV != nil { + artifacts["hyperv"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.HyperV.Artifacts), + } + } + + if releaseArch.Media.Ibmcloud != nil { + artifacts["ibmcloud"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Ibmcloud.Artifacts), + } + ibmcloudObjects := stream.ReplicatedObject{ + Regions: make(map[string]stream.SingleObject), + } + if releaseArch.Media.Ibmcloud.Images != nil { + for region, object := range releaseArch.Media.Ibmcloud.Images { + so := stream.SingleObject{ + Release: rel.Release, + Object: object.Object, + Bucket: object.Bucket, + Url: object.Url, + } + ibmcloudObjects.Regions[region] = so + + } + cloudImages.Ibmcloud = &ibmcloudObjects + } + } + + if releaseArch.Media.KubeVirt != nil { + artifacts["kubevirt"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.KubeVirt.Artifacts), + } + if releaseArch.Media.KubeVirt.Image != nil { + cloudImages.KubeVirt = &stream.ContainerImage{ + Release: rel.Release, + Image: releaseArch.Media.KubeVirt.Image.Image, + DigestRef: releaseArch.Media.KubeVirt.Image.DigestRef, + } + } + } + + if releaseArch.Media.Metal != nil { + artifacts["metal"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Metal.Artifacts), + } + } + + if releaseArch.Media.Nutanix != nil { + artifacts["nutanix"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Nutanix.Artifacts), + } + } + + if releaseArch.Media.Openstack != nil { + artifacts["openstack"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Openstack.Artifacts), + } + } + + // if releaseArch.Media.Packet != nil { + // packet := StreamMediaDetails{ + // Release: rel.Release, + // Formats: releaseArch.Media.Packet.Artifacts, + // } + // artifacts.Packet = &packet + + // packetImage := StreamCloudImage{Image: fmt.Sprintf("fedora_coreos_%s", rel.Stream)} + // cloudImages.Packet = &packetImage + // } + + if releaseArch.Media.PowerVS != nil { + artifacts["powervs"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.PowerVS.Artifacts), + } + powervsObjects := stream.ReplicatedObject{ + Regions: make(map[string]stream.SingleObject), + } + if releaseArch.Media.PowerVS.Images != nil { + for region, object := range releaseArch.Media.PowerVS.Images { + so := stream.SingleObject{ + Release: rel.Release, + Object: object.Object, + Bucket: object.Bucket, + Url: object.Url, + } + powervsObjects.Regions[region] = so + + } + cloudImages.PowerVS = &powervsObjects + } + } + + if releaseArch.Media.Qemu != nil { + artifacts["qemu"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Qemu.Artifacts), + } + } + + if releaseArch.Media.QemuSecex != nil { + artifacts["qemu-secex"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.QemuSecex.Artifacts), + } + } + + if releaseArch.Media.VirtualBox != nil { + artifacts["virtualbox"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.VirtualBox.Artifacts), + } + } + + if releaseArch.Media.Vmware != nil { + artifacts["vmware"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Vmware.Artifacts), + } + } + + if releaseArch.Media.Vultr != nil { + artifacts["vultr"] = stream.PlatformArtifacts{ + Release: rel.Release, + Formats: mapFormats(releaseArch.Media.Vultr.Artifacts), + } + } + + return stream.Arch{ + Artifacts: artifacts, + Images: cloudImages, + RHELCoreOSExtensions: rhcosExt, + } +} + +// ToStreamArchitectures converts a release to a stream +func (rel *Release) ToStreamArchitectures() map[string]stream.Arch { + streamArch := make(map[string]stream.Arch) + for arch, releaseArch := range rel.Architectures { + streamArch[arch] = releaseArch.toStreamArch(rel) + } + return streamArch +} diff --git a/stream/artifact_utils.go b/stream/artifact_utils.go new file mode 100644 index 0000000..68ae647 --- /dev/null +++ b/stream/artifact_utils.go @@ -0,0 +1,95 @@ +package stream + +import ( + "crypto/sha256" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" +) + +// Fetch an artifact, validating its checksum. If applicable, +// the artifact will not be decompressed. Does not +// validate GPG signature. +func (a *Artifact) Fetch(w io.Writer) error { + resp, err := http.Get(a.Location) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("%s returned status: %s", a.Location, resp.Status) + } + hasher := sha256.New() + reader := io.TeeReader(resp.Body, hasher) + + _, err = io.Copy(w, reader) + if err != nil { + return err + } + + // Validate sha256 checksum + foundChecksum := fmt.Sprintf("%x", hasher.Sum(nil)) + if a.Sha256 != foundChecksum { + return fmt.Errorf("checksum mismatch for %s; expected=%s found=%s", a.Location, a.Sha256, foundChecksum) + } + + return nil +} + +// Name returns the "basename" of the artifact, i.e. the contents +// after the last `/`. This can be useful when downloading to a file. +func (a *Artifact) Name() (string, error) { + loc, err := url.Parse(a.Location) + if err != nil { + return "", fmt.Errorf("failed to parse artifact url: %w", err) + } + // Note this one uses `path` since even on Windows URLs have forward slashes. + return path.Base(loc.Path), nil +} + +// Download fetches the specified artifact and saves it to the target +// directory. The full file path will be returned as a string. +// If the target file path exists, it will be overwritten. +// If the download fails, the temporary file will be deleted. +func (a *Artifact) Download(destdir string) (string, error) { + name, err := a.Name() + if err != nil { + return "", err + } + destfile := filepath.Join(destdir, name) + w, err := os.CreateTemp(destdir, ".coreos-artifact-") + if err != nil { + return "", err + } + finalized := false + defer func() { + if !finalized { + // Ignore an error to unlink + _ = os.Remove(w.Name()) + } + }() + + if err := a.Fetch(w); err != nil { + return "", err + } + if err := w.Sync(); err != nil { + return "", err + } + if err := w.Chmod(0644); err != nil { + return "", err + } + if err := w.Close(); err != nil { + return "", err + } + if err := os.Rename(w.Name(), destfile); err != nil { + return "", err + } + finalized = true + + return destfile, nil +} diff --git a/stream/fixtures/fcos-stream.json b/stream/fixtures/fcos-stream.json new file mode 100644 index 0000000..d09a369 --- /dev/null +++ b/stream/fixtures/fcos-stream.json @@ -0,0 +1,397 @@ +{ + "stream": "stable", + "metadata": { + "last-modified": "2020-12-17T14:18:43Z", + "generator": "fedora-coreos-stream-generator v0" + }, + "architectures": { + "x86_64": { + "artifacts": { + "aliyun": { + "release": "33.20201201.3.0", + "formats": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-aliyun.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-aliyun.x86_64.qcow2.xz.sig", + "sha256": "34f3bfd55b0c29804dc2dcace9a671c28b976b62e82e5c42651fd50cf5be4920" + } + } + } + }, + "aws": { + "release": "33.20201201.3.0", + "formats": { + "vmdk.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-aws.x86_64.vmdk.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-aws.x86_64.vmdk.xz.sig", + "sha256": "5719f3ece03294ae64c0a8b0b1ddbb58f2d932a5923ece043f8446114f9e564a" + } + } + } + }, + "azure": { + "release": "33.20201201.3.0", + "formats": { + "vhd.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-azure.x86_64.vhd.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-azure.x86_64.vhd.xz.sig", + "sha256": "7d62c3d06b738268a65d3cd047a87e66b1a04c51b0088a11c87b7b27d224ece3" + } + } + } + }, + "digitalocean": { + "release": "33.20201201.3.0", + "formats": { + "qcow2.gz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-digitalocean.x86_64.qcow2.gz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-digitalocean.x86_64.qcow2.gz.sig", + "sha256": "2b9c851edbc9227fec489cd736df2db11b96df76f82c135675d91103f62ad07b" + } + } + } + }, + "exoscale": { + "release": "33.20201201.3.0", + "formats": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-exoscale.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-exoscale.x86_64.qcow2.xz.sig", + "sha256": "52342742531ad3b49b4f7727208aeed98ae917ff496cdbb32c08ba4c17c39da5" + } + } + } + }, + "gcp": { + "release": "33.20201201.3.0", + "formats": { + "tar.gz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-gcp.x86_64.tar.gz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-gcp.x86_64.tar.gz.sig", + "sha256": "515b2e42b35a15d30ba770db2e896d161baea5801cc27874c8c8f06e43ce1de7" + } + } + } + }, + "ibmcloud": { + "release": "33.20201201.3.0", + "formats": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-ibmcloud.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-ibmcloud.x86_64.qcow2.xz.sig", + "sha256": "d74829e99bd29464a21e4879d0000b15e4afa944cfed0b5d8ae0b7d8931b6758" + } + } + } + }, + "metal": { + "release": "33.20201201.3.0", + "formats": { + "4k.raw.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-metal4k.x86_64.raw.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-metal4k.x86_64.raw.xz.sig", + "sha256": "45621b66ee3db98e3515d334f70e095ef58e03d20d77ae5b55668b421372b6ae" + } + }, + "iso": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live.x86_64.iso", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live.x86_64.iso.sig", + "sha256": "2f62f8f4813bbf584945052d54746cb83101b2c643705a33711ae26fce15fcba" + } + }, + "pxe": { + "kernel": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-kernel-x86_64", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-kernel-x86_64.sig", + "sha256": "d6bddcc1cc8f2642b11f38ca4602573211171814a53ab57c2655853008167407" + }, + "initramfs": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-initramfs.x86_64.img", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-initramfs.x86_64.img.sig", + "sha256": "0872233b1af67b728201b121434e8f990e339cebb85a19b4011b7a94fc83f8c3" + }, + "rootfs": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-rootfs.x86_64.img", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live-rootfs.x86_64.img.sig", + "sha256": "64306ff0a5f20f4c897a2c20dbbe6764b300df85fa90f3b3f400dc8d80b11495" + } + }, + "raw.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-metal.x86_64.raw.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-metal.x86_64.raw.xz.sig", + "sha256": "2848b111a6917455686f38a3ce64d2321c33809b9cf796c5f6804b1c02d79d9d" + } + } + } + }, + "openstack": { + "release": "33.20201201.3.0", + "formats": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-openstack.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-openstack.x86_64.qcow2.xz.sig", + "sha256": "2be55c5aa1f53eb9a869826dacbab75706ee6bd59185b935ac9be546cc132a85" + } + } + } + }, + "kubevirt": { + "release": "33.20201201.3.0", + "formats": { + "ociarchive": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-kubevirt.x86_64.ociarchive", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-kubevirt.x86_64.ociarchive.sig", + "sha256": "2be55c5aa1f53eb9a869826dacbab75706ee6bd59185b935ac9be546cc132a85" + } + } + } + }, + "qemu": { + "release": "33.20201201.3.0", + "formats": { + "qcow2.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-qemu.x86_64.qcow2.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-qemu.x86_64.qcow2.xz.sig", + "sha256": "a7e93e32665086d4a07a14dbe6c125177402f04603fc5bb575035028701afa5b" + } + } + } + }, + "vmware": { + "release": "33.20201201.3.0", + "formats": { + "ova": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-vmware.x86_64.ova", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-vmware.x86_64.ova.sig", + "sha256": "f800c4cfd53ce2586e101d065eacb3c44082db936af777a71678d9c22d83934c" + } + } + } + }, + "vultr": { + "release": "33.20201201.3.0", + "formats": { + "raw.xz": { + "disk": { + "location": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-vultr.x86_64.raw.xz", + "signature": "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-vultr.x86_64.raw.xz.sig", + "sha256": "33ac69ed5fbf3ea6e0a6292894a07491c4b17987323986343ce49109f5f5df7e" + } + } + } + } + }, + "images": { + "aliyun": { + "regions": { + "ap-northeast-1": { + "release": "33.20201201.3.0", + "image": "m-6wecinpim9haf6ws23r2" + }, + "ap-south-1": { + "release": "33.20201201.3.0", + "image": "m-a2d7rl7sf9fhtgs2fqqp" + }, + "ap-southeast-1": { + "release": "33.20201201.3.0", + "image": "m-t4n8qbyvu3hm6s9aibgt" + }, + "ap-southeast-2": { + "release": "33.20201201.3.0", + "image": "m-p0wdy2bd65vy5foymbyn" + }, + "ap-southeast-3": { + "release": "33.20201201.3.0", + "image": "m-8pse8lfxohn0pv9w3s4d" + }, + "ap-southeast-5": { + "release": "33.20201201.3.0", + "image": "m-k1a07hbstlz2nrcpw97q" + }, + "cn-beijing": { + "release": "33.20201201.3.0", + "image": "m-2ze3iqjpvxst7wgwkd6m" + }, + "cn-chengdu": { + "release": "33.20201201.3.0", + "image": "m-2vcftvw1c1pd1iq1tr8u" + }, + "cn-guangzhou": { + "release": "33.20201201.3.0", + "image": "m-7xvfemexpzytrtonb04q" + }, + "cn-hangzhou": { + "release": "33.20201201.3.0", + "image": "m-bp1b29lcl6hx1cu1kknx" + }, + "cn-heyuan": { + "release": "33.20201201.3.0", + "image": "m-f8z2s66jqkz6lqbdrmxk" + }, + "cn-hongkong": { + "release": "33.20201201.3.0", + "image": "m-j6cci77g4mwv4dgw9pf4" + }, + "cn-huhehaote": { + "release": "33.20201201.3.0", + "image": "m-hp3hsaie1sgguc3mf9ww" + }, + "cn-nanjing": { + "release": "33.20201201.3.0", + "image": "m-gc79us39ybgr6oxs4lgu" + }, + "cn-qingdao": { + "release": "33.20201201.3.0", + "image": "m-m5e71jt32enfboyrdf5h" + }, + "cn-shanghai": { + "release": "33.20201201.3.0", + "image": "m-uf6caftakxqri7bf37vf" + }, + "cn-shenzhen": { + "release": "33.20201201.3.0", + "image": "m-wz97oj9dscgdiypk45e8" + }, + "cn-wulanchabu": { + "release": "33.20201201.3.0", + "image": "m-0jl5ejm6aczs1n9a7z0z" + }, + "cn-zhangjiakou": { + "release": "33.20201201.3.0", + "image": "m-8vbg3hxkp6i5zpdklate" + }, + "eu-central-1": { + "release": "33.20201201.3.0", + "image": "m-gw885u4yrv9yo6t3u1ge" + }, + "eu-west-1": { + "release": "33.20201201.3.0", + "image": "m-d7o3sn3qtbax50wep8ra" + }, + "me-east-1": { + "release": "33.20201201.3.0", + "image": "m-eb3bcaf3xkdl8bbesjp4" + }, + "us-east-1": { + "release": "33.20201201.3.0", + "image": "m-0xie8imuezj6of7584wz" + }, + "us-west-1": { + "release": "33.20201201.3.0", + "image": "m-rj93zda388u2hepp7edc" + } + } + }, + "aws": { + "regions": { + "af-south-1": { + "release": "33.20201201.3.0", + "image": "ami-0bd959051ff340868" + }, + "ap-east-1": { + "release": "33.20201201.3.0", + "image": "ami-0cb22a422870c3b7f" + }, + "ap-northeast-1": { + "release": "33.20201201.3.0", + "image": "ami-0370b4b5b620d53b0" + }, + "ap-northeast-2": { + "release": "33.20201201.3.0", + "image": "ami-09d33644806aa7550" + }, + "ap-south-1": { + "release": "33.20201201.3.0", + "image": "ami-09de6e47b22138992" + }, + "ap-southeast-1": { + "release": "33.20201201.3.0", + "image": "ami-059b22e3f95e447b8" + }, + "ap-southeast-2": { + "release": "33.20201201.3.0", + "image": "ami-01e3071d351c82174" + }, + "ca-central-1": { + "release": "33.20201201.3.0", + "image": "ami-0d371d51950767885" + }, + "eu-central-1": { + "release": "33.20201201.3.0", + "image": "ami-069b9a4f891c19440" + }, + "eu-north-1": { + "release": "33.20201201.3.0", + "image": "ami-0a278fa12694ed1a5" + }, + "eu-south-1": { + "release": "33.20201201.3.0", + "image": "ami-0da68c04abc8a25f2" + }, + "eu-west-1": { + "release": "33.20201201.3.0", + "image": "ami-016a154c559c3b81a" + }, + "eu-west-2": { + "release": "33.20201201.3.0", + "image": "ami-0b28ffc2766f88e01" + }, + "eu-west-3": { + "release": "33.20201201.3.0", + "image": "ami-0fe2197196b3ca15c" + }, + "me-south-1": { + "release": "33.20201201.3.0", + "image": "ami-08830d7d8bf851531" + }, + "sa-east-1": { + "release": "33.20201201.3.0", + "image": "ami-075fedcd8c24239bc" + }, + "us-east-1": { + "release": "33.20201201.3.0", + "image": "ami-037a0ba6d14ca2e05" + }, + "us-east-2": { + "release": "33.20201201.3.0", + "image": "ami-091b0dbc05fe2dc06" + }, + "us-west-1": { + "release": "33.20201201.3.0", + "image": "ami-0f775df0ac05d0de6" + }, + "us-west-2": { + "release": "33.20201201.3.0", + "image": "ami-071f6f91a3d8715d7" + } + } + }, + "gcp": { + "release": "33.20201201.3.0", + "project": "fedora-coreos-cloud", + "family": "fedora-coreos-stable", + "name": "fedora-coreos-33-20201201-3-0-gcp-x86-64" + }, + "kubevirt": { + "release": "33.20211201.3.0", + "image": "quay.io/openshift-release-dev/rhcos:latest", + "digest-ref": "quay.io/openshift-release-dev/rhcos@sha256:67a81539946ec0397196c145394553b8e0241acf27b14ae9de43bc56e167f773" + } + } + } + } +} diff --git a/stream/rhcos/rhcos.go b/stream/rhcos/rhcos.go new file mode 100644 index 0000000..320d84b --- /dev/null +++ b/stream/rhcos/rhcos.go @@ -0,0 +1,18 @@ +package rhcos + +// Extensions is data specific to Red Hat Enterprise Linux CoreOS +type Extensions struct { + AzureDisk *AzureDisk `json:"azure-disk,omitempty"` +} + +// AzureDisk represents an Azure disk image that can be imported +// into an image gallery or otherwise replicated, and then used +// as a boot source for virtual machines. +type AzureDisk struct { + // Release is the source release version + Release string `json:"release"` + // URL to an image already stored in Azure infrastructure + // that can be copied into an image gallery. Avoid creating VMs directly + // from this URL as that may lead to performance limitations. + URL string `json:"url,omitempty"` +} diff --git a/stream/stream.go b/stream/stream.go new file mode 100644 index 0000000..1ed9b1f --- /dev/null +++ b/stream/stream.go @@ -0,0 +1,116 @@ +// Package stream models a CoreOS "stream", which is +// a description of the recommended set of binary images for CoreOS. Use +// this API to find cloud images, bare metal disk images, etc. +package stream + +import ( + "github.com/coreos/stream-metadata-go/stream/rhcos" +) + +// Stream contains artifacts available in a stream +type Stream struct { + Stream string `json:"stream"` + Metadata Metadata `json:"metadata"` + Architectures map[string]Arch `json:"architectures"` +} + +// Metadata for a release or stream +type Metadata struct { + LastModified string `json:"last-modified"` + Generator string `json:"generator,omitempty"` +} + +// Arch contains release details for a particular hardware architecture +type Arch struct { + Artifacts map[string]PlatformArtifacts `json:"artifacts"` + Images Images `json:"images,omitempty"` + // RHELCoreOSExtensions is data specific to Red Hat Enterprise Linux CoreOS + RHELCoreOSExtensions *rhcos.Extensions `json:"rhel-coreos-extensions,omitempty"` +} + +// PlatformArtifacts contains images for a platform +type PlatformArtifacts struct { + Release string `json:"release"` + Formats map[string]ImageFormat `json:"formats"` +} + +// ImageFormat contains all artifacts for a single OS image +type ImageFormat struct { + Disk *Artifact `json:"disk,omitempty"` + Kernel *Artifact `json:"kernel,omitempty"` + Initramfs *Artifact `json:"initramfs,omitempty"` + Rootfs *Artifact `json:"rootfs,omitempty"` +} + +// Artifact represents one image file, plus its metadata +type Artifact struct { + Location string `json:"location"` + Signature string `json:"signature,omitempty"` + Sha256 string `json:"sha256"` + UncompressedSha256 string `json:"uncompressed-sha256,omitempty"` +} + +// Images contains images available in cloud providers +type Images struct { + Aliyun *ReplicatedImage `json:"aliyun,omitempty"` + Aws *AwsImage `json:"aws,omitempty"` + Gcp *GcpImage `json:"gcp,omitempty"` + Ibmcloud *ReplicatedObject `json:"ibmcloud,omitempty"` + KubeVirt *ContainerImage `json:"kubevirt,omitempty"` + PowerVS *ReplicatedObject `json:"powervs,omitempty"` +} + +// ReplicatedImage represents an image in all regions of an AWS-like cloud +type ReplicatedImage struct { + Regions map[string]SingleImage `json:"regions,omitempty"` +} + +// SingleImage represents a globally-accessible image or an image in a +// single region of an AWS-like cloud +type SingleImage struct { + Release string `json:"release"` + Image string `json:"image"` +} + +// ContainerImage represents a tagged container image +type ContainerImage struct { + Release string `json:"release"` + // Preferred way to reference the image, which might be by tag or digest + Image string `json:"image"` + DigestRef string `json:"digest-ref"` +} + +// AwsImage is a typedef for backwards compatibility. +type AwsImage = ReplicatedImage + +// AwsRegionImage is a typedef for backwards compatibility. +type AwsRegionImage = SingleImage + +// RegionImage is a typedef for backwards compatibility. +type RegionImage = SingleImage + +// GcpImage represents a GCP cloud image +type GcpImage struct { + Release string `json:"release"` + Project string `json:"project"` + Family string `json:"family,omitempty"` + Name string `json:"name"` +} + +// ReplicatedObject represents an object in all regions of an IBMCloud-like +// cloud +type ReplicatedObject struct { + Regions map[string]SingleObject `json:"regions,omitempty"` +} + +// SingleObject represents a globally-accessible cloud storage object, or +// an object in a single region of an IBMCloud-like cloud +type SingleObject struct { + Release string `json:"release"` + Object string `json:"object"` + Bucket string `json:"bucket"` + Url string `json:"url"` +} + +// RegionObject is a typedef for backwards compatibility. +type RegionObject = SingleObject diff --git a/stream/stream_test.go b/stream/stream_test.go new file mode 100644 index 0000000..b946af7 --- /dev/null +++ b/stream/stream_test.go @@ -0,0 +1,73 @@ +package stream + +import ( + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseFCS(t *testing.T) { + d, err := os.ReadFile("fixtures/fcos-stream.json") + assert.Nil(t, err) + stream := Stream{} + err = json.Unmarshal(d, &stream) + assert.Nil(t, err) + assert.Equal(t, stream.Stream, "stable") + assert.Equal(t, stream.Architectures["x86_64"].Artifacts["metal"].Formats["raw.xz"].Disk.Sha256, "2848b111a6917455686f38a3ce64d2321c33809b9cf796c5f6804b1c02d79d9d") + assert.Equal(t, stream.Architectures["x86_64"].Images.Aws.Regions["us-east-2"].Image, "ami-091b0dbc05fe2dc06") + assert.Equal(t, stream.Architectures["x86_64"].Images.Aliyun.Regions["eu-west-1"].Image, "m-d7o3sn3qtbax50wep8ra") + + ami, err := stream.GetAMI("x86_64", "us-east-2") + assert.Nil(t, err) + assert.Equal(t, ami, "ami-091b0dbc05fe2dc06") + + aliyunImage, err := stream.GetAliyunImage("x86_64", "eu-west-1") + assert.Nil(t, err) + assert.Equal(t, aliyunImage, "m-d7o3sn3qtbax50wep8ra") + + assert.Equal(t, "stable/x86_64", stream.FormatPrefix("x86_64")) + + // I hope I live to see the day when we might change this code to test for success and not error + _, err = stream.GetAMI("x86_64", "mars-1") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "stable/x86_64: No AWS images in region mars-1") + + _, err = stream.GetAMI("aarch64", "us-east-2") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "stream:stable does not have architecture 'aarch64'") + + _, err = stream.GetAliyunImage("x86_64", "us-midwest-1") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "stable/x86_64: No Aliyun images in region us-midwest-1") + + _, err = stream.GetAliyunImage("aarch64", "us-east-2") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "stream:stable does not have architecture 'aarch64'") + + a, err := stream.QueryDisk("x86_64", "metal", "iso") + assert.Nil(t, err) + assert.Equal(t, a.Location, "https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/33.20201201.3.0/x86_64/fedora-coreos-33.20201201.3.0-live.x86_64.iso") + + name, err := a.Name() + assert.Nil(t, err) + assert.Equal(t, name, "fedora-coreos-33.20201201.3.0-live.x86_64.iso") + + _, err = stream.QueryDisk("x86_64", "metal", "nosuchthing") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "disk not found") + _, err = stream.QueryDisk("x86_64", "mysterious-obelisk", "rune") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "artifact 'mysterious-obelisk' not found") + _, err = stream.QueryDisk("nonarch", "", "nosuchthing") + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "does not have architecture 'nonarch'") + + assert.Equal(t, stream.Architectures["x86_64"].Images.KubeVirt, &ContainerImage{ + Release: "33.20211201.3.0", + Image: "quay.io/openshift-release-dev/rhcos:latest", + DigestRef: "quay.io/openshift-release-dev/rhcos@sha256:67a81539946ec0397196c145394553b8e0241acf27b14ae9de43bc56e167f773", + }) + assert.Equal(t, stream.Architectures["x86_64"].Artifacts["kubevirt"].Formats["ociarchive"].Disk.Sha256, "2be55c5aa1f53eb9a869826dacbab75706ee6bd59185b935ac9be546cc132a85") +} diff --git a/stream/stream_utils.go b/stream/stream_utils.go new file mode 100644 index 0000000..b1ca955 --- /dev/null +++ b/stream/stream_utils.go @@ -0,0 +1,94 @@ +package stream + +import "fmt" + +// FormatPrefix describes a stream+architecture combination, intended for prepending to error messages +func (st *Stream) FormatPrefix(archname string) string { + return fmt.Sprintf("%s/%s", st.Stream, archname) +} + +// GetArchitecture loads the architecture-specific builds from a stream, +// with a useful descriptive error message if the architecture is not found. +func (st *Stream) GetArchitecture(archname string) (*Arch, error) { + archdata, ok := st.Architectures[archname] + if !ok { + return nil, fmt.Errorf("stream:%s does not have architecture '%s'", st.Stream, archname) + } + return &archdata, nil +} + +// GetAliyunRegionImage returns the release data (Image ID and release ID) for a particular +// architecture and region. +func (st *Stream) GetAliyunRegionImage(archname, region string) (*SingleImage, error) { + starch, err := st.GetArchitecture(archname) + if err != nil { + return nil, err + } + aliyunimages := starch.Images.Aliyun + if aliyunimages == nil { + return nil, fmt.Errorf("%s: No Aliyun images", st.FormatPrefix(archname)) + } + var regionVal SingleImage + var ok bool + if regionVal, ok = aliyunimages.Regions[region]; !ok { + return nil, fmt.Errorf("%s: No Aliyun images in region %s", st.FormatPrefix(archname), region) + } + + return ®ionVal, nil +} + +// GetAliyunImage returns the Aliyun image for a particular architecture and region. +func (st *Stream) GetAliyunImage(archname, region string) (string, error) { + regionVal, err := st.GetAliyunRegionImage(archname, region) + if err != nil { + return "", err + } + return regionVal.Image, nil +} + +// GetAwsRegionImage returns the release data (AMI and release ID) for a particular +// architecture and region. +func (st *Stream) GetAwsRegionImage(archname, region string) (*SingleImage, error) { + starch, err := st.GetArchitecture(archname) + if err != nil { + return nil, err + } + awsimages := starch.Images.Aws + if awsimages == nil { + return nil, fmt.Errorf("%s: No AWS images", st.FormatPrefix(archname)) + } + var regionVal SingleImage + var ok bool + if regionVal, ok = awsimages.Regions[region]; !ok { + return nil, fmt.Errorf("%s: No AWS images in region %s", st.FormatPrefix(archname), region) + } + + return ®ionVal, nil +} + +// GetAMI returns the AWS machine image for a particular architecture and region. +func (st *Stream) GetAMI(archname, region string) (string, error) { + regionVal, err := st.GetAwsRegionImage(archname, region) + if err != nil { + return "", err + } + return regionVal.Image, nil +} + +// QueryDisk finds the singleton disk artifact for a given format and architecture. +func (st *Stream) QueryDisk(architectureName, artifactName, formatName string) (*Artifact, error) { + arch, err := st.GetArchitecture(architectureName) + if err != nil { + return nil, err + } + artifacts := arch.Artifacts[artifactName] + if artifacts.Release == "" { + return nil, fmt.Errorf("%s: artifact '%s' not found", st.FormatPrefix(architectureName), artifactName) + } + format := artifacts.Formats[formatName] + if format.Disk == nil { + return nil, fmt.Errorf("%s: artifact '%s' format '%s' disk not found", st.FormatPrefix(architectureName), artifactName, formatName) + } + + return format.Disk, nil +} diff --git a/tag_release.sh b/tag_release.sh new file mode 100755 index 0000000..97502f0 --- /dev/null +++ b/tag_release.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# Maintained in https://github.com/coreos/repo-templates +# Do not edit downstream. + +set -e + +[ $# == 2 ] || { echo "usage: $0 " && exit 1; } + +VER=$1 +COMMIT=$2 + +[[ "${VER}" =~ ^v[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+(-.+)?$ ]] || { + echo "malformed version: \"${VER}\"" + exit 2 +} + +[[ "${COMMIT}" =~ ^[[:xdigit:]]+$ ]] || { + echo "malformed commit id: \"${COMMIT}\"" + exit 3 +} + +if [ -f Makefile ]; then + make +else + ./build +fi + +git tag --sign --message "stream-metadata-go ${VER}" "${VER}" "${COMMIT}" +git verify-tag --verbose "${VER}" -- cgit v1.2.3