github.com/vnpaycloud-console/gophercloud/v2@v2.0.5/openstack/networking/v2/ports/requests.go (about) 1 package ports 2 3 import ( 4 "context" 5 "fmt" 6 "net/url" 7 "slices" 8 9 "github.com/vnpaycloud-console/gophercloud/v2" 10 "github.com/vnpaycloud-console/gophercloud/v2/pagination" 11 ) 12 13 // ListOptsBuilder allows extensions to add additional parameters to the 14 // List request. 15 type ListOptsBuilder interface { 16 ToPortListQuery() (string, error) 17 } 18 19 // ListOpts allows the filtering and sorting of paginated collections through 20 // the API. Filtering is achieved by passing in struct field values that map to 21 // the port attributes you want to see returned. SortKey allows you to sort 22 // by a particular port attribute. SortDir sets the direction, and is either 23 // `asc' or `desc'. Marker and Limit are used for pagination. 24 type ListOpts struct { 25 Status string `q:"status"` 26 Name string `q:"name"` 27 Description string `q:"description"` 28 AdminStateUp *bool `q:"admin_state_up"` 29 NetworkID string `q:"network_id"` 30 TenantID string `q:"tenant_id"` 31 ProjectID string `q:"project_id"` 32 DeviceOwner string `q:"device_owner"` 33 MACAddress string `q:"mac_address"` 34 ID string `q:"id"` 35 DeviceID string `q:"device_id"` 36 Limit int `q:"limit"` 37 Marker string `q:"marker"` 38 SortKey string `q:"sort_key"` 39 SortDir string `q:"sort_dir"` 40 Tags string `q:"tags"` 41 TagsAny string `q:"tags-any"` 42 NotTags string `q:"not-tags"` 43 NotTagsAny string `q:"not-tags-any"` 44 SecurityGroups []string `q:"security_groups"` 45 FixedIPs []FixedIPOpts 46 } 47 48 type FixedIPOpts struct { 49 IPAddress string 50 IPAddressSubstr string 51 SubnetID string 52 } 53 54 func (f FixedIPOpts) toParams() []string { 55 var res []string 56 if f.IPAddress != "" { 57 res = append(res, fmt.Sprintf("ip_address=%s", f.IPAddress)) 58 } 59 if f.IPAddressSubstr != "" { 60 res = append(res, fmt.Sprintf("ip_address_substr=%s", f.IPAddressSubstr)) 61 } 62 if f.SubnetID != "" { 63 res = append(res, fmt.Sprintf("subnet_id=%s", f.SubnetID)) 64 } 65 return res 66 } 67 68 // ToPortListQuery formats a ListOpts into a query string. 69 func (opts ListOpts) ToPortListQuery() (string, error) { 70 q, err := gophercloud.BuildQueryString(opts) 71 params := q.Query() 72 for _, fixedIP := range opts.FixedIPs { 73 for _, fixedIPParam := range fixedIP.toParams() { 74 params.Add("fixed_ips", fixedIPParam) 75 } 76 } 77 q = &url.URL{RawQuery: params.Encode()} 78 return q.String(), err 79 } 80 81 // List returns a Pager which allows you to iterate over a collection of 82 // ports. It accepts a ListOpts struct, which allows you to filter and sort 83 // the returned collection for greater efficiency. 84 // 85 // Default policy settings return only those ports that are owned by the tenant 86 // who submits the request, unless the request is submitted by a user with 87 // administrative rights. 88 func List(c *gophercloud.ServiceClient, opts ListOptsBuilder) pagination.Pager { 89 url := listURL(c) 90 if opts != nil { 91 query, err := opts.ToPortListQuery() 92 if err != nil { 93 return pagination.Pager{Err: err} 94 } 95 url += query 96 } 97 return pagination.NewPager(c, url, func(r pagination.PageResult) pagination.Page { 98 return PortPage{pagination.LinkedPageBase{PageResult: r}} 99 }) 100 } 101 102 // Get retrieves a specific port based on its unique ID. 103 func Get(ctx context.Context, c *gophercloud.ServiceClient, id string) (r GetResult) { 104 resp, err := c.Get(ctx, getURL(c, id), &r.Body, nil) 105 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 106 return 107 } 108 109 // CreateOptsBuilder allows extensions to add additional parameters to the 110 // Create request. 111 type CreateOptsBuilder interface { 112 ToPortCreateMap() (map[string]any, error) 113 } 114 115 // CreateOpts represents the attributes used when creating a new port. 116 type CreateOpts struct { 117 NetworkID string `json:"network_id" required:"true"` 118 Name string `json:"name,omitempty"` 119 Description string `json:"description,omitempty"` 120 AdminStateUp *bool `json:"admin_state_up,omitempty"` 121 MACAddress string `json:"mac_address,omitempty"` 122 FixedIPs any `json:"fixed_ips,omitempty"` 123 DeviceID string `json:"device_id,omitempty"` 124 DeviceOwner string `json:"device_owner,omitempty"` 125 TenantID string `json:"tenant_id,omitempty"` 126 ProjectID string `json:"project_id,omitempty"` 127 SecurityGroups *[]string `json:"security_groups,omitempty"` 128 AllowedAddressPairs []AddressPair `json:"allowed_address_pairs,omitempty"` 129 PropagateUplinkStatus *bool `json:"propagate_uplink_status,omitempty"` 130 ValueSpecs *map[string]string `json:"value_specs,omitempty"` 131 VirtualIp *bool `json:"virtual_ip,omitempty"` 132 } 133 134 // ToPortCreateMap builds a request body from CreateOpts. 135 func (opts CreateOpts) ToPortCreateMap() (map[string]any, error) { 136 body, err := gophercloud.BuildRequestBody(opts, "port") 137 if err != nil { 138 return nil, err 139 } 140 141 return AddValueSpecs(body) 142 } 143 144 // AddValueSpecs expands the 'value_specs' object and removes 'value_specs' 145 // from the request body. It will return error if the value specs would overwrite 146 // an existing field or contains forbidden keys. 147 func AddValueSpecs(body map[string]any) (map[string]any, error) { 148 // Banned the same as in heat. See https://github.com/openstack/heat/blob/dd7319e373b88812cb18897f742b5196a07227ea/heat/engine/resources/openstack/neutron/neutron.py#L59 149 bannedKeys := []string{"shared", "tenant_id"} 150 port := body["port"].(map[string]any) 151 152 if port["value_specs"] != nil { 153 for k, v := range port["value_specs"].(map[string]any) { 154 if slices.Contains(bannedKeys, k) { 155 return nil, fmt.Errorf("forbidden key in value_specs: %s", k) 156 } 157 if _, ok := port[k]; ok { 158 return nil, fmt.Errorf("value_specs would overwrite key: %s", k) 159 } 160 port[k] = v 161 } 162 delete(port, "value_specs") 163 } 164 body["port"] = port 165 166 return body, nil 167 } 168 169 // Create accepts a CreateOpts struct and creates a new network using the values 170 // provided. You must remember to provide a NetworkID value. 171 func Create(ctx context.Context, c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) { 172 b, err := opts.ToPortCreateMap() 173 if err != nil { 174 r.Err = err 175 return 176 } 177 resp, err := c.Post(ctx, createURL(c), b, &r.Body, nil) 178 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 179 return 180 } 181 182 // UpdateOptsBuilder allows extensions to add additional parameters to the 183 // Update request. 184 type UpdateOptsBuilder interface { 185 ToPortUpdateMap() (map[string]any, error) 186 } 187 188 // UpdateOpts represents the attributes used when updating an existing port. 189 type UpdateOpts struct { 190 Name *string `json:"name,omitempty"` 191 Description *string `json:"description,omitempty"` 192 AdminStateUp *bool `json:"admin_state_up,omitempty"` 193 FixedIPs any `json:"fixed_ips,omitempty"` 194 DeviceID *string `json:"device_id,omitempty"` 195 DeviceOwner *string `json:"device_owner,omitempty"` 196 SecurityGroups *[]string `json:"security_groups,omitempty"` 197 AllowedAddressPairs *[]AddressPair `json:"allowed_address_pairs,omitempty"` 198 PropagateUplinkStatus *bool `json:"propagate_uplink_status,omitempty"` 199 ValueSpecs *map[string]string `json:"value_specs,omitempty"` 200 201 // RevisionNumber implements extension:standard-attr-revisions. If != "" it 202 // will set revision_number=%s. If the revision number does not match, the 203 // update will fail. 204 RevisionNumber *int `json:"-" h:"If-Match"` 205 VirtualIp *bool `json:"virtual_ip,omitempty"` 206 } 207 208 // ToPortUpdateMap builds a request body from UpdateOpts. 209 func (opts UpdateOpts) ToPortUpdateMap() (map[string]any, error) { 210 body, err := gophercloud.BuildRequestBody(opts, "port") 211 if err != nil { 212 return nil, err 213 } 214 return AddValueSpecs(body) 215 } 216 217 // Update accepts a UpdateOpts struct and updates an existing port using the 218 // values provided. 219 func Update(ctx context.Context, c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) { 220 b, err := opts.ToPortUpdateMap() 221 if err != nil { 222 r.Err = err 223 return 224 } 225 h, err := gophercloud.BuildHeaders(opts) 226 if err != nil { 227 r.Err = err 228 return 229 } 230 for k := range h { 231 if k == "If-Match" { 232 h[k] = fmt.Sprintf("revision_number=%s", h[k]) 233 } 234 } 235 resp, err := c.Put(ctx, updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{ 236 MoreHeaders: h, 237 OkCodes: []int{200, 201}, 238 }) 239 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 240 return 241 } 242 243 // Delete accepts a unique ID and deletes the port associated with it. 244 func Delete(ctx context.Context, c *gophercloud.ServiceClient, id string) (r DeleteResult) { 245 resp, err := c.Delete(ctx, deleteURL(c, id), nil) 246 _, r.Header, r.Err = gophercloud.ParseResponse(resp, err) 247 return 248 }