github.com/hanks177/podman/v4@v4.1.3-0.20220613032544-16d90015bc83/pkg/api/handlers/compat/networks.go (about) 1 package compat 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net" 7 "net/http" 8 9 nettypes "github.com/containers/common/libnetwork/types" 10 netutil "github.com/containers/common/libnetwork/util" 11 "github.com/hanks177/podman/v4/libpod" 12 "github.com/hanks177/podman/v4/libpod/define" 13 "github.com/hanks177/podman/v4/pkg/api/handlers/utils" 14 api "github.com/hanks177/podman/v4/pkg/api/types" 15 "github.com/hanks177/podman/v4/pkg/domain/entities" 16 "github.com/hanks177/podman/v4/pkg/domain/infra/abi" 17 "github.com/hanks177/podman/v4/pkg/util" 18 "github.com/docker/docker/api/types" 19 20 dockerNetwork "github.com/docker/docker/api/types/network" 21 "github.com/gorilla/schema" 22 "github.com/pkg/errors" 23 "github.com/sirupsen/logrus" 24 ) 25 26 func InspectNetwork(w http.ResponseWriter, r *http.Request) { 27 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 28 29 // scope is only used to see if the user passes any illegal value, verbose is not used but implemented 30 // for compatibility purposes only. 31 query := struct { 32 scope string `schema:"scope"` 33 verbose bool `schema:"verbose"` 34 }{ 35 scope: "local", 36 } 37 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 38 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 39 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 40 return 41 } 42 43 if query.scope != "local" { 44 utils.Error(w, http.StatusBadRequest, define.ErrInvalidArg) 45 return 46 } 47 name := utils.GetName(r) 48 net, err := runtime.Network().NetworkInspect(name) 49 if err != nil { 50 utils.NetworkNotFound(w, name, err) 51 return 52 } 53 report, err := convertLibpodNetworktoDockerNetwork(runtime, net) 54 if err != nil { 55 utils.InternalServerError(w, err) 56 return 57 } 58 utils.WriteResponse(w, http.StatusOK, report) 59 } 60 61 func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, network nettypes.Network) (*types.NetworkResource, error) { 62 cons, err := runtime.GetAllContainers() 63 if err != nil { 64 return nil, err 65 } 66 containerEndpoints := make(map[string]types.EndpointResource, len(cons)) 67 for _, con := range cons { 68 data, err := con.Inspect(false) 69 if err != nil { 70 return nil, err 71 } 72 if netData, ok := data.NetworkSettings.Networks[network.Name]; ok { 73 ipv4Address := "" 74 if netData.IPAddress != "" { 75 ipv4Address = fmt.Sprintf("%s/%d", netData.IPAddress, netData.IPPrefixLen) 76 } 77 ipv6Address := "" 78 if netData.GlobalIPv6Address != "" { 79 ipv6Address = fmt.Sprintf("%s/%d", netData.GlobalIPv6Address, netData.GlobalIPv6PrefixLen) 80 } 81 containerEndpoint := types.EndpointResource{ 82 Name: con.Name(), 83 EndpointID: netData.EndpointID, 84 MacAddress: netData.MacAddress, 85 IPv4Address: ipv4Address, 86 IPv6Address: ipv6Address, 87 } 88 containerEndpoints[con.ID()] = containerEndpoint 89 } 90 } 91 ipamConfigs := make([]dockerNetwork.IPAMConfig, 0, len(network.Subnets)) 92 for _, sub := range network.Subnets { 93 ipamConfig := dockerNetwork.IPAMConfig{ 94 Subnet: sub.Subnet.String(), 95 Gateway: sub.Gateway.String(), 96 // TODO add range 97 } 98 ipamConfigs = append(ipamConfigs, ipamConfig) 99 } 100 ipamDriver := network.IPAMOptions["driver"] 101 if ipamDriver == nettypes.HostLocalIPAMDriver { 102 ipamDriver = "default" 103 } 104 ipam := dockerNetwork.IPAM{ 105 Driver: ipamDriver, 106 Options: network.IPAMOptions, 107 Config: ipamConfigs, 108 } 109 110 report := types.NetworkResource{ 111 Name: network.Name, 112 ID: network.ID, 113 Driver: network.Driver, 114 // TODO add Created: , 115 Internal: network.Internal, 116 EnableIPv6: network.IPv6Enabled, 117 Labels: network.Labels, 118 Options: network.Options, 119 IPAM: ipam, 120 Scope: "local", 121 Attachable: false, 122 Ingress: false, 123 ConfigFrom: dockerNetwork.ConfigReference{}, 124 ConfigOnly: false, 125 Containers: containerEndpoints, 126 Peers: nil, 127 Services: nil, 128 } 129 return &report, nil 130 } 131 132 func ListNetworks(w http.ResponseWriter, r *http.Request) { 133 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 134 filterMap, err := util.PrepareFilters(r) 135 if err != nil { 136 utils.Error(w, http.StatusInternalServerError, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 137 return 138 } 139 140 options := entities.NetworkListOptions{ 141 Filters: *filterMap, 142 } 143 144 ic := abi.ContainerEngine{Libpod: runtime} 145 nets, err := ic.NetworkList(r.Context(), options) 146 if err != nil { 147 utils.InternalServerError(w, err) 148 return 149 } 150 reports := make([]*types.NetworkResource, 0, len(nets)) 151 for _, net := range nets { 152 report, err := convertLibpodNetworktoDockerNetwork(runtime, net) 153 if err != nil { 154 utils.InternalServerError(w, err) 155 return 156 } 157 reports = append(reports, report) 158 } 159 utils.WriteResponse(w, http.StatusOK, reports) 160 } 161 162 func CreateNetwork(w http.ResponseWriter, r *http.Request) { 163 var ( 164 networkCreate types.NetworkCreateRequest 165 network nettypes.Network 166 ) 167 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 168 if err := json.NewDecoder(r.Body).Decode(&networkCreate); err != nil { 169 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 170 return 171 } 172 173 network.Name = networkCreate.Name 174 if networkCreate.Driver == "" { 175 networkCreate.Driver = nettypes.DefaultNetworkDriver 176 } 177 network.Driver = networkCreate.Driver 178 network.Labels = networkCreate.Labels 179 network.Internal = networkCreate.Internal 180 network.IPv6Enabled = networkCreate.EnableIPv6 181 182 // FIXME use docker options and convert them to valid libpod options 183 // network.Options = networkCreate.Options 184 185 // dns is only enabled for the bridge driver 186 if network.Driver == nettypes.BridgeNetworkDriver { 187 network.DNSEnabled = true 188 } 189 190 if networkCreate.IPAM != nil && len(networkCreate.IPAM.Config) > 0 { 191 for _, conf := range networkCreate.IPAM.Config { 192 s := nettypes.Subnet{} 193 if len(conf.Subnet) > 0 { 194 var err error 195 subnet, err := nettypes.ParseCIDR(conf.Subnet) 196 if err != nil { 197 utils.InternalServerError(w, errors.Wrap(err, "failed to parse subnet")) 198 return 199 } 200 s.Subnet = subnet 201 } 202 if len(conf.Gateway) > 0 { 203 gw := net.ParseIP(conf.Gateway) 204 if gw == nil { 205 utils.InternalServerError(w, errors.Errorf("failed to parse gateway ip %s", conf.Gateway)) 206 return 207 } 208 s.Gateway = gw 209 } 210 if len(conf.IPRange) > 0 { 211 _, net, err := net.ParseCIDR(conf.IPRange) 212 if err != nil { 213 utils.InternalServerError(w, errors.Wrap(err, "failed to parse ip range")) 214 return 215 } 216 startIP, err := netutil.FirstIPInSubnet(net) 217 if err != nil { 218 utils.InternalServerError(w, errors.Wrap(err, "failed to get first ip in range")) 219 return 220 } 221 lastIP, err := netutil.LastIPInSubnet(net) 222 if err != nil { 223 utils.InternalServerError(w, errors.Wrap(err, "failed to get last ip in range")) 224 return 225 } 226 s.LeaseRange = &nettypes.LeaseRange{ 227 StartIP: startIP, 228 EndIP: lastIP, 229 } 230 } 231 network.Subnets = append(network.Subnets, s) 232 } 233 // FIXME can we use the IPAM driver and options? 234 } 235 236 ic := abi.ContainerEngine{Libpod: runtime} 237 newNetwork, err := ic.NetworkCreate(r.Context(), network) 238 if err != nil { 239 utils.InternalServerError(w, err) 240 return 241 } 242 243 body := struct { 244 ID string `json:"Id"` 245 Warning string 246 }{ 247 ID: newNetwork.ID, 248 } 249 utils.WriteResponse(w, http.StatusCreated, body) 250 } 251 252 func RemoveNetwork(w http.ResponseWriter, r *http.Request) { 253 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 254 ic := abi.ContainerEngine{Libpod: runtime} 255 256 query := struct { 257 Force bool `schema:"force"` 258 Timeout *uint `schema:"timeout"` 259 }{ 260 // This is where you can override the golang default value for one of fields 261 } 262 263 decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder) 264 if err := decoder.Decode(&query, r.URL.Query()); err != nil { 265 utils.Error(w, http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String())) 266 return 267 } 268 269 options := entities.NetworkRmOptions{ 270 Force: query.Force, 271 Timeout: query.Timeout, 272 } 273 274 name := utils.GetName(r) 275 reports, err := ic.NetworkRm(r.Context(), []string{name}, options) 276 if err != nil { 277 utils.Error(w, http.StatusInternalServerError, err) 278 return 279 } 280 if len(reports) == 0 { 281 utils.Error(w, http.StatusInternalServerError, errors.Errorf("internal error")) 282 return 283 } 284 report := reports[0] 285 if report.Err != nil { 286 if errors.Cause(report.Err) == define.ErrNoSuchNetwork { 287 utils.Error(w, http.StatusNotFound, define.ErrNoSuchNetwork) 288 return 289 } 290 utils.InternalServerError(w, report.Err) 291 return 292 } 293 294 utils.WriteResponse(w, http.StatusNoContent, nil) 295 } 296 297 // Connect adds a container to a network 298 func Connect(w http.ResponseWriter, r *http.Request) { 299 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 300 301 var netConnect types.NetworkConnect 302 if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil { 303 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 304 return 305 } 306 307 netOpts := nettypes.PerNetworkOptions{} 308 309 name := utils.GetName(r) 310 if netConnect.EndpointConfig != nil { 311 if netConnect.EndpointConfig.Aliases != nil { 312 netOpts.Aliases = netConnect.EndpointConfig.Aliases 313 } 314 315 // if IP address is provided 316 if len(netConnect.EndpointConfig.IPAddress) > 0 { 317 staticIP := net.ParseIP(netConnect.EndpointConfig.IPAddress) 318 if staticIP == nil { 319 utils.Error(w, http.StatusInternalServerError, 320 errors.Errorf("failed to parse the ip address %q", netConnect.EndpointConfig.IPAddress)) 321 return 322 } 323 netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) 324 } 325 326 if netConnect.EndpointConfig.IPAMConfig != nil { 327 // if IPAMConfig.IPv4Address is provided 328 if len(netConnect.EndpointConfig.IPAMConfig.IPv4Address) > 0 { 329 staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv4Address) 330 if staticIP == nil { 331 utils.Error(w, http.StatusInternalServerError, 332 errors.Errorf("failed to parse the ipv4 address %q", netConnect.EndpointConfig.IPAMConfig.IPv4Address)) 333 return 334 } 335 netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) 336 } 337 // if IPAMConfig.IPv6Address is provided 338 if len(netConnect.EndpointConfig.IPAMConfig.IPv6Address) > 0 { 339 staticIP := net.ParseIP(netConnect.EndpointConfig.IPAMConfig.IPv6Address) 340 if staticIP == nil { 341 utils.Error(w, http.StatusInternalServerError, 342 errors.Errorf("failed to parse the ipv6 address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address)) 343 return 344 } 345 netOpts.StaticIPs = append(netOpts.StaticIPs, staticIP) 346 } 347 } 348 // If MAC address is provided 349 if len(netConnect.EndpointConfig.MacAddress) > 0 { 350 staticMac, err := net.ParseMAC(netConnect.EndpointConfig.MacAddress) 351 if err != nil { 352 utils.Error(w, http.StatusInternalServerError, 353 errors.Errorf("failed to parse the mac address %q", netConnect.EndpointConfig.IPAMConfig.IPv6Address)) 354 return 355 } 356 netOpts.StaticMAC = nettypes.HardwareAddr(staticMac) 357 } 358 } 359 err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netOpts) 360 if err != nil { 361 if errors.Cause(err) == define.ErrNoSuchCtr { 362 utils.ContainerNotFound(w, netConnect.Container, err) 363 return 364 } 365 if errors.Cause(err) == define.ErrNoSuchNetwork { 366 utils.Error(w, http.StatusNotFound, err) 367 return 368 } 369 utils.Error(w, http.StatusInternalServerError, err) 370 return 371 } 372 utils.WriteResponse(w, http.StatusOK, "OK") 373 } 374 375 // Disconnect removes a container from a network 376 func Disconnect(w http.ResponseWriter, r *http.Request) { 377 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 378 379 var netDisconnect types.NetworkDisconnect 380 if err := json.NewDecoder(r.Body).Decode(&netDisconnect); err != nil { 381 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 382 return 383 } 384 385 name := utils.GetName(r) 386 err := runtime.DisconnectContainerFromNetwork(netDisconnect.Container, name, netDisconnect.Force) 387 if err != nil { 388 if errors.Cause(err) == define.ErrNoSuchCtr { 389 utils.Error(w, http.StatusNotFound, err) 390 return 391 } 392 if errors.Cause(err) == define.ErrNoSuchNetwork { 393 utils.Error(w, http.StatusNotFound, err) 394 return 395 } 396 utils.Error(w, http.StatusInternalServerError, err) 397 return 398 } 399 utils.WriteResponse(w, http.StatusOK, "OK") 400 } 401 402 // Prune removes unused networks 403 func Prune(w http.ResponseWriter, r *http.Request) { 404 runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) 405 filterMap, err := util.PrepareFilters(r) 406 if err != nil { 407 utils.Error(w, http.StatusInternalServerError, errors.Wrap(err, "Decode()")) 408 return 409 } 410 411 ic := abi.ContainerEngine{Libpod: runtime} 412 pruneOptions := entities.NetworkPruneOptions{ 413 Filters: *filterMap, 414 } 415 pruneReports, err := ic.NetworkPrune(r.Context(), pruneOptions) 416 if err != nil { 417 utils.Error(w, http.StatusInternalServerError, err) 418 return 419 } 420 type response struct { 421 NetworksDeleted []string 422 } 423 prunedNetworks := []string{} 424 for _, pr := range pruneReports { 425 if pr.Error != nil { 426 logrus.Error(pr.Error) 427 continue 428 } 429 prunedNetworks = append(prunedNetworks, pr.Name) 430 } 431 utils.WriteResponse(w, http.StatusOK, response{NetworksDeleted: prunedNetworks}) 432 }