Skip to main content

openstack_cli_image/v2/image/
patch.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12//
13// SPDX-License-Identifier: Apache-2.0
14//
15// WARNING: This file is automatically generated from OpenAPI schema using
16// `openstack-codegenerator`.
17
18//! Set Image command
19//!
20//! Wraps invoking of the `v2/images/{image_id}` with `PATCH` method
21
22use clap::Args;
23use eyre::WrapErr;
24use tracing::info;
25
26use openstack_cli_core::cli::CliArgs;
27use openstack_cli_core::error::OpenStackCliError;
28use openstack_cli_core::output::OutputProcessor;
29use openstack_sdk::AsyncOpenStack;
30
31use clap::ValueEnum;
32use json_patch::diff;
33use openstack_cli_core::common::parse_key_val;
34use openstack_sdk::api::QueryAsync;
35use openstack_sdk::api::find;
36use openstack_sdk::api::image::v2::image::find;
37use openstack_sdk::api::image::v2::image::patch;
38use openstack_types::image::v2::image::response::patch::*;
39use serde_json::Value;
40use serde_json::json;
41
42/// Updates an image. *(Since Image API v2.0)*
43///
44/// Conceptually, you update an image record by patching the JSON
45/// representation of the image, passing a request body conforming to one of
46/// the following media types:
47///
48/// Attempting to make a PATCH call using some other media type will provoke a
49/// response code of 415 (Unsupported media type).
50///
51/// The `application/openstack-images-v2.1-json-patch` media type provides a
52/// useful and compatible subset of the functionality defined in JavaScript
53/// Object Notation (JSON) Patch [RFC6902](http://tools.ietf.org/html/rfc6902),
54/// which defines the `application/json-patch+json` media type.
55///
56/// For information about the PATCH method and the available media types, see
57/// [Image API v2 HTTP PATCH media types](http://specs.openstack.org/openstack/glance-specs/specs/api/v2/http-patch-image-api-v2.html).
58///
59/// Attempting to modify some image properties will cause the entire request to
60/// fail with a 403 (Forbidden) response code:
61///
62/// Attempting to add a location path to an image that is not in `queued` or
63/// `active` state will result in a 409 (Conflict) response code *(since Image
64/// API v2.4)*.
65///
66/// Normal response codes: 200
67///
68/// Error response codes: 400, 401, 403, 404, 409, 413, 415
69#[derive(Args)]
70#[command(about = "Update image")]
71pub struct ImageCommand {
72    /// Request Query parameters
73    #[command(flatten)]
74    query: QueryParameters,
75
76    /// Path parameters
77    #[command(flatten)]
78    path: PathParameters,
79
80    /// Format of the image container.
81    ///
82    /// Values may vary based on the configuration available in a particular
83    /// OpenStack cloud. See the [Image Schema](#image-schema) response from
84    /// the cloud itself for the valid values available. See
85    /// [Container Format](https://docs.openstack.org/glance/latest/user/formats.html#container-format)
86    /// in the Glance documentation for more information.
87    ///
88    /// Example formats are: `ami`, `ari`, `aki`, `bare`, `ovf`, `ova`,
89    /// `docker`, or `compressed`.
90    ///
91    /// The value might be `null` (JSON null data type).
92    ///
93    /// **Train changes**: The `compressed` container format is a supported
94    /// value.
95    #[arg(help_heading = "Body parameters", long)]
96    container_format: Option<String>,
97
98    /// The format of the disk.
99    ///
100    /// Values may vary based on the configuration available in a particular
101    /// OpenStack cloud. See the [Image Schema](#image-schema) response from
102    /// the cloud itself for the valid values available. See
103    /// [Disk Format](https://docs.openstack.org/glance/latest/user/formats.html#disk-format)
104    /// in the Glance documentation for more information.
105    ///
106    /// Example formats are: `ami`, `ari`, `aki`, `vhd`, `vhdx`, `vmdk`, `raw`,
107    /// `qcow2`, `vdi`, `ploop` or `iso`.
108    ///
109    /// The value might be `null` (JSON null data type).
110    ///
111    /// **Newton changes**: The `vhdx` disk format is a supported value.\
112    /// **Ocata changes**: The `ploop` disk format is a supported value.
113    #[arg(help_heading = "Body parameters", long)]
114    disk_format: Option<String>,
115
116    /// A list of objects, each of which describes an image location. Each
117    /// object contains a `url` key, whose value is a URL specifying a
118    /// location, and a `metadata` key, whose value is a dict of key:value
119    /// pairs containing information appropriate to the use of whatever
120    /// external store is indicated by the URL. *This list appears only if the*
121    /// `show_multiple_locations` *option is set to* `true` *in the Image
122    /// service’s configuration file.* **Because it presents a security risk,
123    /// this option is disabled by default.**
124    ///
125    /// Parameter is an array, may be provided multiple times.
126    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long, value_name="JSON", value_parser=openstack_cli_core::common::parse_json)]
127    locations: Option<Vec<Value>>,
128
129    /// Amount of disk space in GB that is required to boot the image. The
130    /// value might be `null` (JSON null data type).
131    #[arg(help_heading = "Body parameters", long)]
132    min_disk: Option<i32>,
133
134    /// Amount of RAM in MB that is required to boot the image. The value might
135    /// be `null` (JSON null data type).
136    #[arg(help_heading = "Body parameters", long)]
137    min_ram: Option<i32>,
138
139    /// The name of the image. Value might be `null` (JSON null data type).
140    #[arg(help_heading = "Body parameters", long)]
141    name: Option<String>,
142
143    /// This field controls whether an image is displayed in the default
144    /// image-list response. A “hidden” image is out of date somehow (for
145    /// example, it may not have the latest updates applied) and hence should
146    /// not be a user’s first choice, but it’s not deleted because it may be
147    /// needed for server rebuilds. By hiding it from the default image list,
148    /// it’s easier for end users to find and use a more up-to-date version of
149    /// this image. *(Since Image API v2.7)*
150    #[arg(action=clap::ArgAction::Set, help_heading = "Body parameters", long)]
151    os_hidden: Option<bool>,
152
153    /// An identifier for the owner of the image, usually the project (also
154    /// called the “tenant”) ID. The value might be `null` (JSON null data
155    /// type).
156    #[arg(help_heading = "Body parameters", long)]
157    owner: Option<String>,
158
159    /// A boolean value that must be `false` or the image cannot be deleted.
160    #[arg(action=clap::ArgAction::Set, help_heading = "Body parameters", long)]
161    protected: Option<bool>,
162
163    /// List of tags for this image, possibly an empty list.
164    ///
165    /// Parameter is an array, may be provided multiple times.
166    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long)]
167    tags: Option<Vec<String>>,
168
169    /// Image visibility, that is, the access permission for the image.
170    #[arg(help_heading = "Body parameters", long)]
171    visibility: Option<Visibility>,
172    /// Additional properties to be sent with the request
173    #[arg(long="property", value_name="key=value", value_parser=parse_key_val::<String, String>)]
174    #[arg(help_heading = "Body parameters")]
175    properties: Option<Vec<(String, String)>>,
176}
177
178/// Query parameters
179#[derive(Args)]
180struct QueryParameters {}
181
182/// Path parameters
183#[derive(Args)]
184struct PathParameters {
185    /// image_id parameter for /v2/images/{image_id} API
186    #[arg(
187        help_heading = "Path parameters",
188        id = "path_param_id",
189        value_name = "ID"
190    )]
191    id: String,
192}
193
194#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, ValueEnum)]
195enum Visibility {
196    Community,
197    Private,
198    Public,
199    Shared,
200}
201
202impl ImageCommand {
203    /// Perform command action
204    pub async fn take_action<C: CliArgs>(
205        &self,
206        parsed_args: &C,
207        client: &mut AsyncOpenStack,
208    ) -> Result<(), OpenStackCliError> {
209        info!("Set Image");
210
211        let op = OutputProcessor::from_args(parsed_args, Some("image.image"), Some("patch"));
212        op.validate_args(parsed_args)?;
213
214        let mut find_builder = find::Request::builder();
215
216        find_builder.id(&self.path.id);
217
218        let find_ep = find_builder
219            .build()
220            .map_err(|x| OpenStackCliError::EndpointBuild(x.to_string()))?;
221        let find_data: serde_json::Value = find(find_ep).query_async(client).await?;
222
223        // Patching resource requires fetching and calculating diff
224        let resource_id = find_data["id"]
225            .as_str()
226            .ok_or_else(|| eyre::eyre!("resource ID must be a string"))?
227            .to_string();
228
229        let data: ImageResponse = serde_json::from_value(find_data)?;
230        let mut new = data.clone();
231        if let Some(val) = &self.container_format {
232            new.container_format = Some(val.into());
233        }
234        if let Some(val) = &self.disk_format {
235            new.disk_format = Some(val.into());
236        }
237        if let Some(val) = &self.locations {
238            new.locations = Some(serde_json::from_value(val.to_owned().into())?);
239        }
240        if let Some(val) = &self.min_disk {
241            new.min_disk = Some(*val);
242        }
243        if let Some(val) = &self.min_ram {
244            new.min_ram = Some(*val);
245        }
246        if let Some(val) = &self.name {
247            new.name = Some(val.into());
248        }
249        if let Some(val) = &self.os_hidden {
250            new.os_hidden = Some(*val);
251        }
252        if let Some(val) = &self.owner {
253            new.owner = Some(val.into());
254        }
255        if let Some(val) = &self.protected {
256            new.protected = Some(*val);
257        }
258        if let Some(val) = &self.tags {
259            new.tags = Some(serde_json::from_value(val.to_owned().into())?);
260        }
261        if let Some(val) = &self.visibility {
262            // StringEnum
263            let tmp = match val {
264                Visibility::Community => "community",
265                Visibility::Private => "private",
266                Visibility::Public => "public",
267                Visibility::Shared => "shared",
268            };
269            new.visibility = Some(
270                tmp.parse()
271                    .map_err(|_| eyre::eyre!("unsupported value for visibility"))?,
272            );
273        }
274
275        let curr_json =
276            serde_json::to_value(&data).wrap_err("current state must be a valid json object")?;
277        let mut new_json =
278            serde_json::to_value(&new).wrap_err("new state must be a valid json object")?;
279        if let Some(properties) = &self.properties {
280            for (key, val) in properties {
281                new_json[key] = json!(val);
282            }
283        }
284
285        let patch = diff(&curr_json, &new_json);
286
287        let mut patch_ep_builder = patch::Request::builder();
288        patch_ep_builder.id(&resource_id);
289        patch_ep_builder.patch(patch);
290
291        let patch_ep = patch_ep_builder
292            .build()
293            .map_err(|x| OpenStackCliError::EndpointBuild(x.to_string()))?;
294        let new_data = patch_ep.query_async(client).await?;
295        op.output_single::<ImageResponse>(new_data)?;
296        // Show command specific hints
297        op.show_command_hint()?;
298        Ok(())
299    }
300}