summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 16:16:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 16:16:09 +0000
commit8cc545c834f33bad219cd4a17d1ab7d0e2d8c9d3 (patch)
tree696297517d0d4115d70e9bb861db313cba4baa73
parentInitial commit. (diff)
downloadgolang-github-coreos-stream-metadata-go-8cc545c834f33bad219cd4a17d1ab7d0e2d8c9d3.tar.xz
golang-github-coreos-stream-metadata-go-8cc545c834f33bad219cd4a17d1ab7d0e2d8c9d3.zip
Adding upstream version 0.4.3.upstream/0.4.3upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.github/ISSUE_TEMPLATE/release-checklist.md19
-rw-r--r--.github/dependabot.yml13
-rw-r--r--.github/workflows/go.yml51
-rw-r--r--.github/workflows/require-release-note.yml27
-rw-r--r--LICENSE201
-rw-r--r--Makefile18
-rw-r--r--README.md11
-rw-r--r--arch/arch.go48
-rw-r--r--arch/arch_test.go19
-rw-r--r--docs/development.md7
-rw-r--r--docs/release-notes.md101
-rw-r--r--example/example.go76
-rw-r--r--fedoracoreos/fcos.go60
-rw-r--r--fedoracoreos/fcos_test.go10
-rw-r--r--fedoracoreos/internals/fcosinternals.go33
-rw-r--r--go.mod11
-rw-r--r--go.sum10
-rw-r--r--release/fixtures/fcos-release.json255
-rw-r--r--release/fixtures/fcos-releases.json269
-rw-r--r--release/release.go151
-rw-r--r--release/release_test.go56
-rw-r--r--release/rhcos/rhcos.go14
-rw-r--r--release/translate.go289
-rw-r--r--stream/artifact_utils.go95
-rw-r--r--stream/fixtures/fcos-stream.json397
-rw-r--r--stream/rhcos/rhcos.go18
-rw-r--r--stream/stream.go116
-rw-r--r--stream/stream_test.go73
-rw-r--r--stream/stream_utils.go94
-rwxr-xr-xtag_release.sh29
30 files changed, 2571 insertions, 0 deletions
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 <vX.Y.z> <git commit hash>`
+ - [ ] 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 &regionVal, 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 &regionVal, 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 <version> <commit>" && 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}"