coreos_stream_metadata/lib.rs
1//! Library for interacting with CoreOS stream metadata, used
2//! by Fedora CoreOS and RHEL CoreOS.
3//!
4//!
5//! # Get the URL for FCOS stable stream:
6//!
7//! ```no_run
8//! use coreos_stream_metadata::fcos;
9//! let url = fcos::StreamID::Stable.url();
10//! ```
11//!
12//! # Deserialize stream data and print URL for OpenStack image
13//!
14//! ```no_run
15//! use coreos_stream_metadata::Stream;
16//!
17//! let stream: Stream = serde_json::from_reader(std::io::stdin())?;
18//! let openstack = stream.query_thisarch_single("openstack").ok_or_else(|| anyhow::anyhow!("Missing openstack image"))?;
19//! println!("OpenStack image URL: {}", openstack.location);
20//! # Ok::<(), anyhow::Error>(())
21//! ```
22
23#![deny(unused_must_use)]
24#![deny(unsafe_code)]
25#![forbid(missing_docs)]
26
27use serde::Deserialize;
28use std::collections::HashMap;
29
30pub mod fcos;
31pub mod rhcos;
32
33/// Toplevel stream object.
34#[derive(Debug, Deserialize)]
35pub struct Stream {
36 /// Name of the stream.
37 pub stream: String,
38 /// Architectures.
39 pub architectures: HashMap<String, Arch>,
40}
41
42/// Artifacts for a particular architecture.
43#[derive(Debug, Deserialize)]
44pub struct Arch {
45 /// Downloadable artifacts.
46 pub artifacts: HashMap<String, Platform>,
47 /// Images already uploaded to public clouds.
48 pub images: Option<Images>,
49}
50
51/// A specific platform (e.g. `aws`, `gcp`)
52#[derive(Debug, Deserialize)]
53pub struct Platform {
54 /// Specific formats.
55 pub formats: HashMap<String, HashMap<String, Artifact>>,
56}
57
58/// A downloadable artifact with a URL and detached signature.
59#[derive(Debug, Deserialize)]
60#[serde(rename_all = "kebab-case")]
61pub struct Artifact {
62 /// The URL for this artifact.
63 pub location: String,
64 /// SHA-256 checksum.
65 pub sha256: String,
66 /// If the artifact is compressed, this is the uncompressed SHA-256.
67 pub uncompressed_sha256: Option<String>,
68 /// Detached GPG signature.
69 pub signature: Option<String>,
70}
71
72/// Alias for backward compatibility
73pub type AwsImages = ReplicatedImage;
74
75/// Alias for backward compatibility
76pub type AwsRegionImage = SingleImage;
77
78/// An image in all regions of an AWS-like cloud
79#[derive(Debug, Deserialize)]
80#[serde(rename_all = "kebab-case")]
81pub struct ReplicatedImage {
82 /// Mapping from region name to image
83 pub regions: HashMap<String, SingleImage>,
84}
85
86/// An globally-accessible image or an image in a single region of an
87/// AWS-like cloud
88#[derive(Debug, Deserialize, PartialEq, Eq)]
89#[serde(rename_all = "kebab-case")]
90pub struct SingleImage {
91 /// The release version of FCOS.
92 pub release: String,
93 /// Image reference
94 pub image: String,
95}
96
97/// A tagged container image
98#[derive(Debug, Deserialize, PartialEq, Eq)]
99#[serde(rename_all = "kebab-case")]
100pub struct ContainerImage {
101 /// The release version of FCOS.
102 pub release: String,
103 /// Preferred way to reference the image, which might be by tag or digest
104 pub image: String,
105 /// Image reference by digest
106 pub digest_ref: String,
107}
108
109/// Image stored in Google Compute Platform.
110#[derive(Debug, Deserialize)]
111#[serde(rename_all = "kebab-case")]
112pub struct GcpImage {
113 /// The release version of FCOS.
114 // Legacy metadata doesn't have this
115 pub release: Option<String>,
116 /// The project ID.
117 pub project: String,
118 /// The image family.
119 pub family: Option<String>,
120 /// The image name.
121 pub name: String,
122}
123
124/// Objects in an object store for each region, such as on IBMCloud or PowerVS.
125#[derive(Debug, Deserialize)]
126#[serde(rename_all = "kebab-case")]
127pub struct ReplicatedObject {
128 /// Mapping from region name to the object.
129 pub regions: HashMap<String, RegionObject>,
130}
131
132/// Region-specific object in an object store, such as on IBMCloud or PowerVS.
133#[derive(Debug, Deserialize, PartialEq, Eq)]
134#[serde(rename_all = "kebab-case")]
135pub struct RegionObject {
136 /// The release version of FCOS.
137 pub release: String,
138 /// The name of the object in the object store.
139 pub object: String,
140 /// The bucket where the object resides.
141 pub bucket: String,
142 /// The url of the object.
143 pub url: String,
144}
145
146/// Public cloud images.
147#[derive(Debug, Deserialize)]
148#[serde(rename_all = "kebab-case")]
149pub struct Images {
150 /// Images for Aliyun
151 pub aliyun: Option<ReplicatedImage>,
152 /// Images for AWS.
153 pub aws: Option<ReplicatedImage>,
154 /// Images for GCP.
155 pub gcp: Option<GcpImage>,
156 /// Objects for IBMCloud
157 pub ibmcloud: Option<ReplicatedObject>,
158 /// ContainerDisk for KubeVirt
159 pub kubevirt: Option<ContainerImage>,
160 /// Objects for PowerVS
161 pub powervs: Option<ReplicatedObject>,
162}
163
164impl Stream {
165 /// Returns the data for the CPU architecture matching the running process.
166 pub fn this_architecture(&self) -> Option<&Arch> {
167 self.architectures.get(this_architecture())
168 }
169
170 /// Find a `disk` artifact.
171 pub fn query_disk(&self, arch: &str, artifact: &str, format_name: &str) -> Option<&Artifact> {
172 self.architectures
173 .get(arch)
174 .and_then(|a| a.artifacts.get(artifact))
175 .and_then(|p| p.formats.get(format_name))
176 .and_then(|p| p.get("disk"))
177 }
178
179 /// Find the single `disk` image for this architecture of the given type. Only use this
180 /// for images which don't have multiple format.s
181 pub fn query_thisarch_single(&self, artifact: &str) -> Option<&Artifact> {
182 self.this_architecture()
183 .and_then(|a| a.artifacts.get(artifact))
184 .and_then(|p| p.formats.iter().next())
185 .and_then(|(_fmt, v)| v.get("disk"))
186 }
187}
188
189/// Return the RPM/GNU architecture identifier for the current binary.
190///
191/// See also https://github.com/coreos/stream-metadata-go/blob/c5fe1b98ac1b1e6ab62a606b7580dc1f30703f83/arch/arch.go
192pub fn this_architecture() -> &'static str {
193 match std::env::consts::ARCH {
194 // Funny enough, PowerPC is so far the only weird case here.
195 // For everything else, the Rust architecture is the same as RPM/GNU/Linux.
196 "powerpc64" if cfg!(target_endian = "big") => "ppc64",
197 "powerpc64" if cfg!(target_endian = "little") => "ppc64le",
198 o => o,
199 }
200}