Skip to main content

openstack_cli_network/v2/port/
create.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//! Create Port command
19//!
20//! Wraps invoking of the `v2.0/ports` with `POST` 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 openstack_cli_core::common::parse_key_val;
33use openstack_sdk::api::QueryAsync;
34use openstack_sdk::api::network::v2::port::create;
35use openstack_types::network::v2::port::response;
36use serde_json::Value;
37use std::collections::BTreeMap;
38
39/// Creates a port on a network.
40///
41/// To define the network in which to create the port, specify the `network_id`
42/// attribute in the request body.
43///
44/// Normal response codes: 201
45///
46/// Error response codes: 400, 401, 403, 404
47#[derive(Args)]
48#[command(about = "Create port")]
49pub struct PortCommand {
50    /// Request Query parameters
51    #[command(flatten)]
52    query: QueryParameters,
53
54    /// Path parameters
55    #[command(flatten)]
56    path: PathParameters,
57
58    /// A `port` object.
59    #[command(flatten)]
60    port: Port,
61}
62
63/// Query parameters
64#[derive(Args)]
65struct QueryParameters {}
66
67/// Path parameters
68#[derive(Args)]
69struct PathParameters {}
70
71#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, ValueEnum)]
72enum BindingVnicType {
73    AcceleratorDirect,
74    AcceleratorDirectPhysical,
75    Baremetal,
76    Direct,
77    DirectPhysical,
78    Macvtap,
79    Normal,
80    RemoteManaged,
81    SmartNic,
82    Vdpa,
83    VirtioForwarder,
84}
85
86#[derive(Clone, Eq, Ord, PartialEq, PartialOrd, ValueEnum)]
87enum NumaAffinityPolicy {
88    Legacy,
89    Preferred,
90    Required,
91    Socket,
92}
93
94/// Port Body data
95#[derive(Args, Clone)]
96struct Port {
97    /// The administrative state of the resource, which is up (`true`) or down
98    /// (`false`). Default is `true`.
99    #[arg(action=clap::ArgAction::Set, help_heading = "Body parameters", long)]
100    admin_state_up: Option<bool>,
101
102    /// A set of zero or more allowed address pair objects each where address
103    /// pair object contains an `ip_address` and `mac_address`. While the
104    /// `ip_address` is required, the `mac_address` will be taken from the port
105    /// if not specified. The value of `ip_address` can be an IP Address or a
106    /// CIDR (if supported by the underlying extension plugin). A server
107    /// connected to the port can send a packet with source address which
108    /// matches one of the specified allowed address pairs.
109    ///
110    /// Parameter is an array, may be provided multiple times.
111    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long, value_name="JSON", value_parser=openstack_cli_core::common::parse_json)]
112    allowed_address_pairs: Option<Vec<Value>>,
113
114    /// The ID of the host where the port resides. The default is an empty
115    /// string.
116    #[arg(help_heading = "Body parameters", long)]
117    binding_host_id: Option<String>,
118
119    /// A dictionary that enables the application running on the specific host
120    /// to pass and receive vif port information specific to the networking
121    /// back-end. This field is only meant for machine-machine communication
122    /// for compute services like Nova, Ironic or Zun to pass information to a
123    /// Neutron back-end. It should not be used by multiple services
124    /// concurrently or by cloud end users. The existing counterexamples
125    /// (`capabilities: [switchdev]` for Open vSwitch hardware offload and
126    /// `trusted=true` for Trusted Virtual Functions) are due to be cleaned up.
127    /// The networking API does not define a specific format of this field. The
128    /// default is an empty dictionary. If you update it with null then it is
129    /// treated like {} in the response. Since the port-mac-address-override
130    /// extension the `device_mac_address` field of the binding:profile can be
131    /// used to provide the MAC address of the physical device a
132    /// direct-physical port is being bound to. If provided, then the
133    /// `mac_address` field of the port resource will be updated to the MAC
134    /// from the active binding.
135    #[arg(help_heading = "Body parameters", long, value_name="key=value", value_parser=parse_key_val::<String, Value>)]
136    binding_profile: Option<Vec<(String, Value)>>,
137
138    /// The type of vNIC which this port should be attached to. This is used to
139    /// determine which mechanism driver(s) to be used to bind the port. The
140    /// valid values are `normal`, `macvtap`, `direct`, `baremetal`,
141    /// `direct-physical`, `virtio-forwarder`, `smart-nic`, `vdpa`,
142    /// `accelerator-direct`, `accelerator-direct-physical` and
143    /// `remote-managed`. What type of vNIC is actually available depends on
144    /// deployments. The default is `normal`.
145    #[arg(help_heading = "Body parameters", long)]
146    binding_vnic_type: Option<BindingVnicType>,
147
148    /// A human-readable description for the resource. Default is an empty
149    /// string.
150    #[arg(help_heading = "Body parameters", long)]
151    description: Option<String>,
152
153    /// The ID of the device that uses this port. For example, a server
154    /// instance or a logical router.
155    #[arg(help_heading = "Body parameters", long)]
156    device_id: Option<String>,
157
158    /// The entity type that uses this port. For example, `compute:nova`
159    /// (server instance), `network:dhcp` (DHCP agent) or
160    /// `network:router_interface` (router interface).
161    #[arg(help_heading = "Body parameters", long)]
162    device_owner: Option<String>,
163
164    #[arg(help_heading = "Body parameters", long)]
165    device_profile: Option<String>,
166
167    /// Set explicit NULL for the device_profile
168    #[arg(help_heading = "Body parameters", long, action = clap::ArgAction::SetTrue, conflicts_with = "device_profile")]
169    no_device_profile: bool,
170
171    /// A valid DNS domain.
172    #[arg(help_heading = "Body parameters", long)]
173    dns_domain: Option<String>,
174
175    /// A valid DNS name.
176    #[arg(help_heading = "Body parameters", long)]
177    dns_name: Option<String>,
178
179    /// A set of zero or more extra DHCP option pairs. An option pair consists
180    /// of an option value and name.
181    ///
182    /// Parameter is an array, may be provided multiple times.
183    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long, value_name="JSON", value_parser=openstack_cli_core::common::parse_json)]
184    extra_dhcp_opts: Option<Vec<Value>>,
185
186    /// The IP addresses for the port. If you would like to assign multiple IP
187    /// addresses for the port, specify multiple entries in this field. Each
188    /// entry consists of IP address (`ip_address`) and the subnet ID from
189    /// which the IP address is assigned (`subnet_id`).
190    ///
191    /// - If you specify both a subnet ID and an IP address, OpenStack
192    ///   Networking tries to allocate the IP address on that subnet to the
193    ///   port.
194    /// - If you specify only a subnet ID, OpenStack Networking allocates an
195    ///   available IP from that subnet to the port.
196    /// - If you specify only an IP address, OpenStack Networking tries to
197    ///   allocate the IP address if the address is a valid IP for any of the
198    ///   subnets on the specified network.
199    ///
200    /// Parameter is an array, may be provided multiple times.
201    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long, value_name="JSON", value_parser=openstack_cli_core::common::parse_json)]
202    fixed_ips: Option<Vec<Value>>,
203
204    /// Admin-only. A dict, at the top level keyed by mechanism driver aliases
205    /// (as defined in setup.cfg). To following values can be used to control
206    /// Open vSwitch’s Userspace Tx packet steering feature:
207    ///
208    /// - `{"openvswitch": {"other_config": {"tx-steering": "hash"}}}`
209    /// - `{"openvswitch": {"other_config": {"tx-steering": "thread"}}}`
210    ///
211    /// If omitted the default is defined by Open vSwitch. The field cannot be
212    /// longer than 4095 characters.
213    #[arg(help_heading = "Body parameters", long, value_name="key=value", value_parser=parse_key_val::<String, Value>)]
214    hints: Option<Vec<(String, Value)>>,
215
216    /// The MAC address of the port. If unspecified, a MAC address is
217    /// automatically generated.
218    #[arg(help_heading = "Body parameters", long)]
219    mac_address: Option<String>,
220
221    /// Human-readable name of the resource. Default is an empty string.
222    #[arg(help_heading = "Body parameters", long)]
223    name: Option<String>,
224
225    /// The ID of the attached network.
226    #[arg(help_heading = "Body parameters", long)]
227    network_id: Option<String>,
228
229    /// The port NUMA affinity policy requested during the virtual machine
230    /// scheduling. Values: `None`, `required`, `preferred` or `legacy`.
231    #[arg(help_heading = "Body parameters", long)]
232    numa_affinity_policy: Option<NumaAffinityPolicy>,
233
234    /// The port security status. A valid value is enabled (`true`) or disabled
235    /// (`false`). If port security is enabled for the port, security group
236    /// rules and anti-spoofing rules are applied to the traffic on the port.
237    /// If disabled, no such rules are applied.
238    #[arg(action=clap::ArgAction::Set, help_heading = "Body parameters", long)]
239    port_security_enabled: Option<bool>,
240
241    /// The uplink status propagation of the port. Valid values are enabled
242    /// (`true`) and disabled (`false`).
243    #[arg(action=clap::ArgAction::Set, help_heading = "Body parameters", long)]
244    propagate_uplink_status: Option<bool>,
245
246    /// QoS policy associated with the port.
247    #[arg(help_heading = "Body parameters", long)]
248    qos_policy_id: Option<String>,
249
250    /// Set explicit NULL for the qos_policy_id
251    #[arg(help_heading = "Body parameters", long, action = clap::ArgAction::SetTrue, conflicts_with = "qos_policy_id")]
252    no_qos_policy_id: bool,
253
254    /// The IDs of security groups applied to the port.
255    ///
256    /// Parameter is an array, may be provided multiple times.
257    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long)]
258    security_groups: Option<Vec<String>>,
259
260    /// Parameter is an array, may be provided multiple times.
261    #[arg(action=clap::ArgAction::Append, help_heading = "Body parameters", long)]
262    tags: Option<Vec<String>>,
263
264    /// The ID of the project that owns the resource. Only administrative and
265    /// users with advsvc role can specify a project ID other than their own.
266    /// You cannot change this value through authorization policies.
267    #[arg(help_heading = "Body parameters", long)]
268    tenant_id: Option<String>,
269}
270
271impl PortCommand {
272    /// Perform command action
273    pub async fn take_action<C: CliArgs>(
274        &self,
275        parsed_args: &C,
276        client: &mut AsyncOpenStack,
277    ) -> Result<(), OpenStackCliError> {
278        info!("Create Port");
279
280        let op = OutputProcessor::from_args(parsed_args, Some("network.port"), Some("create"));
281        op.validate_args(parsed_args)?;
282
283        let mut ep_builder = create::Request::builder();
284
285        // Set body parameters
286        // Set Request.port data
287        let args = &self.port;
288        let mut port_builder = create::PortBuilder::default();
289        if let Some(val) = &args.admin_state_up {
290            port_builder.admin_state_up(*val);
291        }
292
293        if let Some(val) = &args.allowed_address_pairs {
294            let allowed_address_pairs_builder: Vec<create::AllowedAddressPairs> = val
295                .iter()
296                .flat_map(|v| serde_json::from_value::<create::AllowedAddressPairs>(v.to_owned()))
297                .collect::<Vec<create::AllowedAddressPairs>>();
298            port_builder.allowed_address_pairs(allowed_address_pairs_builder);
299        }
300
301        if let Some(val) = &args.binding_host_id {
302            port_builder.binding_host_id(val);
303        }
304
305        if let Some(val) = &args.binding_profile {
306            port_builder.binding_profile(val.iter().cloned());
307        }
308
309        if let Some(val) = &args.binding_vnic_type {
310            let tmp = match val {
311                BindingVnicType::AcceleratorDirect => create::BindingVnicType::AcceleratorDirect,
312                BindingVnicType::AcceleratorDirectPhysical => {
313                    create::BindingVnicType::AcceleratorDirectPhysical
314                }
315                BindingVnicType::Baremetal => create::BindingVnicType::Baremetal,
316                BindingVnicType::Direct => create::BindingVnicType::Direct,
317                BindingVnicType::DirectPhysical => create::BindingVnicType::DirectPhysical,
318                BindingVnicType::Macvtap => create::BindingVnicType::Macvtap,
319                BindingVnicType::Normal => create::BindingVnicType::Normal,
320                BindingVnicType::RemoteManaged => create::BindingVnicType::RemoteManaged,
321                BindingVnicType::SmartNic => create::BindingVnicType::SmartNic,
322                BindingVnicType::Vdpa => create::BindingVnicType::Vdpa,
323                BindingVnicType::VirtioForwarder => create::BindingVnicType::VirtioForwarder,
324            };
325            port_builder.binding_vnic_type(tmp);
326        }
327
328        if let Some(val) = &args.description {
329            port_builder.description(val);
330        }
331
332        if let Some(val) = &args.device_id {
333            port_builder.device_id(val);
334        }
335
336        if let Some(val) = &args.device_owner {
337            port_builder.device_owner(val);
338        }
339
340        if let Some(val) = &args.device_profile {
341            port_builder.device_profile(Some(val.into()));
342        } else if args.no_device_profile {
343            port_builder.device_profile(None);
344        }
345
346        if let Some(val) = &args.dns_domain {
347            port_builder.dns_domain(val);
348        }
349
350        if let Some(val) = &args.dns_name {
351            port_builder.dns_name(val);
352        }
353
354        if let Some(val) = &args.extra_dhcp_opts {
355            port_builder.extra_dhcp_opts(
356                val.iter()
357                    .map(|v| {
358                        v.as_object()
359                            .ok_or_else(|| {
360                                eyre::eyre!("extra_dhcp_opts must be a valid json object")
361                            })
362                            .and_then(|obj| {
363                                obj.into_iter()
364                                    .map(|(k, val)| -> eyre::Result<_> {
365                                        Ok((
366                                            k.into(),
367                                            serde_json::from_value::<Value>(val.clone())?,
368                                        ))
369                                    })
370                                    .collect::<Result<BTreeMap<_, _>, _>>()
371                            })
372                    })
373                    .collect::<Result<Vec<_>, _>>()?,
374            );
375        }
376
377        if let Some(val) = &args.fixed_ips {
378            let fixed_ips_builder: Vec<create::FixedIps> = val
379                .iter()
380                .flat_map(|v| serde_json::from_value::<create::FixedIps>(v.to_owned()))
381                .collect::<Vec<create::FixedIps>>();
382            port_builder.fixed_ips(fixed_ips_builder);
383        }
384
385        if let Some(val) = &args.hints {
386            port_builder.hints(val.iter().cloned());
387        }
388
389        if let Some(val) = &args.mac_address {
390            port_builder.mac_address(val);
391        }
392
393        if let Some(val) = &args.name {
394            port_builder.name(val);
395        }
396
397        if let Some(val) = &args.network_id {
398            port_builder.network_id(val);
399        }
400
401        if let Some(val) = &args.numa_affinity_policy {
402            let tmp = match val {
403                NumaAffinityPolicy::Legacy => create::NumaAffinityPolicy::Legacy,
404                NumaAffinityPolicy::Preferred => create::NumaAffinityPolicy::Preferred,
405                NumaAffinityPolicy::Required => create::NumaAffinityPolicy::Required,
406                NumaAffinityPolicy::Socket => create::NumaAffinityPolicy::Socket,
407            };
408            port_builder.numa_affinity_policy(tmp);
409        }
410
411        if let Some(val) = &args.port_security_enabled {
412            port_builder.port_security_enabled(*val);
413        }
414
415        if let Some(val) = &args.propagate_uplink_status {
416            port_builder.propagate_uplink_status(*val);
417        }
418
419        if let Some(val) = &args.qos_policy_id {
420            port_builder.qos_policy_id(Some(val.into()));
421        } else if args.no_qos_policy_id {
422            port_builder.qos_policy_id(None);
423        }
424
425        if let Some(val) = &args.security_groups {
426            port_builder.security_groups(val.iter().map(Into::into).collect::<Vec<_>>());
427        }
428
429        if let Some(val) = &args.tags {
430            port_builder.tags(val.iter().map(Into::into).collect::<Vec<_>>());
431        }
432
433        if let Some(val) = &args.tenant_id {
434            port_builder.tenant_id(val);
435        }
436
437        ep_builder.port(
438            port_builder
439                .build()
440                .wrap_err("error preparing the request data")?,
441        );
442
443        let ep = ep_builder
444            .build()
445            .map_err(|x| OpenStackCliError::EndpointBuild(x.to_string()))?;
446
447        let data: serde_json::Value = ep.query_async(client).await?;
448
449        op.output_single::<response::create::PortResponse>(data.clone())?;
450        // Show command specific hints
451        op.show_command_hint()?;
452        Ok(())
453    }
454}