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