Merge pull request #16773 from ygalblum/network_ignore

Network Create: Add --ignore flag to support idempotent script
This commit is contained in:
OpenShift Merge Robot
2022-12-15 14:27:25 -05:00
committed by GitHub
14 changed files with 139 additions and 12 deletions

View File

@ -78,6 +78,8 @@ func networkCreateFlags(cmd *cobra.Command) {
_ = cmd.RegisterFlagCompletionFunc(subnetFlagName, completion.AutocompleteNone) _ = cmd.RegisterFlagCompletionFunc(subnetFlagName, completion.AutocompleteNone)
flags.BoolVar(&networkCreateOptions.DisableDNS, "disable-dns", false, "disable dns plugin") flags.BoolVar(&networkCreateOptions.DisableDNS, "disable-dns", false, "disable dns plugin")
flags.BoolVar(&networkCreateOptions.IgnoreIfExists, "ignore", false, "Don't fail if network already exists")
} }
func init() { func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{ registry.Commands = append(registry.Commands, registry.CliCommand{
@ -165,7 +167,11 @@ func networkCreate(cmd *cobra.Command, args []string) error {
return errors.New("cannot set gateway or range without subnet") return errors.New("cannot set gateway or range without subnet")
} }
response, err := registry.ContainerEngine().NetworkCreate(registry.Context(), network) extraCreateOptions := types.NetworkCreateOptions{
IgnoreIfExists: networkCreateOptions.IgnoreIfExists,
}
response, err := registry.ContainerEngine().NetworkCreate(registry.Context(), network, &extraCreateOptions)
if err != nil { if err != nil {
return err return err
} }

View File

@ -39,6 +39,10 @@ Define a gateway for the subnet. To provide a gateway address, a
*subnet* option is required. Can be specified multiple times. *subnet* option is required. Can be specified multiple times.
The argument order of the **--subnet**, **--gateway** and **--ip-range** options must match. The argument order of the **--subnet**, **--gateway** and **--ip-range** options must match.
#### **--ignore**
Ignore the create request if a network with the same name already exists instead of failing.
Note, trying to create a network with an existing name and different parameters, will not change the configuration of the existing one
#### **--internal** #### **--internal**
Restrict external access of this network. Note when using this option, the dnsname plugin will be Restrict external access of this network. Note when using this option, the dnsname plugin will be

View File

@ -278,7 +278,7 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
} }
ic := abi.ContainerEngine{Libpod: runtime} ic := abi.ContainerEngine{Libpod: runtime}
newNetwork, err := ic.NetworkCreate(r.Context(), network) newNetwork, err := ic.NetworkCreate(r.Context(), network, nil)
if err != nil { if err != nil {
utils.InternalServerError(w, err) utils.InternalServerError(w, err)
return return

View File

@ -31,8 +31,17 @@ func CreateNetwork(w http.ResponseWriter, r *http.Request) {
return return
} }
query := struct {
IgnoreIfExists bool `schema:"ignoreIfExists"`
}{}
decoder := r.Context().Value(api.DecoderKey).(*schema.Decoder)
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err))
return
}
ic := abi.ContainerEngine{Libpod: runtime} ic := abi.ContainerEngine{Libpod: runtime}
report, err := ic.NetworkCreate(r.Context(), network) report, err := ic.NetworkCreate(r.Context(), network, &types.NetworkCreateOptions{IgnoreIfExists: query.IgnoreIfExists})
if err != nil { if err != nil {
if errors.Is(err, types.ErrNetworkExists) { if errors.Is(err, types.ErrNetworkExists) {
utils.Error(w, http.StatusConflict, err) utils.Error(w, http.StatusConflict, err)

View File

@ -3,6 +3,7 @@ package network
import ( import (
"context" "context"
"net/http" "net/http"
"net/url"
"strings" "strings"
"github.com/containers/common/libnetwork/types" "github.com/containers/common/libnetwork/types"
@ -13,11 +14,24 @@ import (
// Create makes a new CNI network configuration // Create makes a new CNI network configuration
func Create(ctx context.Context, network *types.Network) (types.Network, error) { func Create(ctx context.Context, network *types.Network) (types.Network, error) {
return CreateWithOptions(ctx, network, nil)
}
func CreateWithOptions(ctx context.Context, network *types.Network, extraCreateOptions *ExtraCreateOptions) (types.Network, error) {
var report types.Network var report types.Network
conn, err := bindings.GetClient(ctx) conn, err := bindings.GetClient(ctx)
if err != nil { if err != nil {
return report, err return report, err
} }
var params url.Values
if extraCreateOptions != nil {
params, err = extraCreateOptions.ToParams()
if err != nil {
return report, err
}
}
// create empty network if the caller did not provide one // create empty network if the caller did not provide one
if network == nil { if network == nil {
network = &types.Network{} network = &types.Network{}
@ -27,7 +41,7 @@ func Create(ctx context.Context, network *types.Network) (types.Network, error)
return report, err return report, err
} }
reader := strings.NewReader(networkConfig) reader := strings.NewReader(networkConfig)
response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/networks/create", nil, nil) response, err := conn.DoRequest(ctx, reader, http.MethodPost, "/networks/create", params, nil)
if err != nil { if err != nil {
return report, err return report, err
} }

View File

@ -84,3 +84,12 @@ type PruneOptions struct {
// specific on choosing // specific on choosing
Filters map[string][]string Filters map[string][]string
} }
// ExtraCreateOptions are optional additional configuration flags for creating Networks
// that are not part of the network configuration
//
//go:generate go run ../generator/generator.go ExtraCreateOptions
type ExtraCreateOptions struct {
// IgnoreIfExists if true, do not fail if the network already exists
IgnoreIfExists *bool `schema:"ignoreIfExists"`
}

View File

@ -0,0 +1,33 @@
// Code generated by go generate; DO NOT EDIT.
package network
import (
"net/url"
"github.com/containers/podman/v4/pkg/bindings/internal/util"
)
// Changed returns true if named field has been set
func (o *ExtraCreateOptions) Changed(fieldName string) bool {
return util.Changed(o, fieldName)
}
// ToParams formats struct fields to be passed to API service
func (o *ExtraCreateOptions) ToParams() (url.Values, error) {
return util.ToParams(o)
}
// WithIgnoreIfExists set field IgnoreIfExists to given value
func (o *ExtraCreateOptions) WithIgnoreIfExists(value bool) *ExtraCreateOptions {
o.IgnoreIfExists = &value
return o
}
// GetIgnoreIfExists returns value of field IgnoreIfExists
func (o *ExtraCreateOptions) GetIgnoreIfExists() bool {
if o.IgnoreIfExists == nil {
var z bool
return z
}
return *o.IgnoreIfExists
}

View File

@ -101,11 +101,24 @@ var _ = Describe("Podman networks", func() {
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
Expect(report.Name).To(Equal(name)) Expect(report.Name).To(Equal(name))
// create network with same name should 500 // create network with same name should 409
_, err = network.Create(connText, &net) _, err = network.Create(connText, &net)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
code, _ := bindings.CheckResponseCode(err) code, _ := bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusConflict)) Expect(code).To(BeNumerically("==", http.StatusConflict))
// create network with same name and ignore false should 409
options := new(network.ExtraCreateOptions).WithIgnoreIfExists(false)
_, err = network.CreateWithOptions(connText, &net, options)
Expect(err).To(HaveOccurred())
code, _ = bindings.CheckResponseCode(err)
Expect(code).To(BeNumerically("==", http.StatusConflict))
// create network with same name and ignore true succeed
options = new(network.ExtraCreateOptions).WithIgnoreIfExists(true)
report, err = network.CreateWithOptions(connText, &net, options)
Expect(err).ToNot(HaveOccurred())
Expect(report.Name).To(Equal(name))
}) })
It("inspect network", func() { It("inspect network", func() {

View File

@ -63,7 +63,7 @@ type ContainerEngine interface { //nolint:interfacebloat
Info(ctx context.Context) (*define.Info, error) Info(ctx context.Context) (*define.Info, error)
KubeApply(ctx context.Context, body io.Reader, opts ApplyOptions) error KubeApply(ctx context.Context, body io.Reader, opts ApplyOptions) error
NetworkConnect(ctx context.Context, networkname string, options NetworkConnectOptions) error NetworkConnect(ctx context.Context, networkname string, options NetworkConnectOptions) error
NetworkCreate(ctx context.Context, network types.Network) (*types.Network, error) NetworkCreate(ctx context.Context, network types.Network, createOptions *types.NetworkCreateOptions) (*types.Network, error)
NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) error NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) error
NetworkExists(ctx context.Context, networkname string) (*BoolReport, error) NetworkExists(ctx context.Context, networkname string) (*BoolReport, error)
NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]types.Network, []error, error) NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]types.Network, []error, error)

View File

@ -52,6 +52,8 @@ type NetworkCreateOptions struct {
IPv6 bool IPv6 bool
// Mapping of driver options and values. // Mapping of driver options and values.
Options map[string]string Options map[string]string
// IgnoreIfExists if true, do not fail if the network already exists
IgnoreIfExists bool
} }
// NetworkCreateReport describes a created network for the cli // NetworkCreateReport describes a created network for the cli

View File

@ -138,12 +138,12 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o
return reports, nil return reports, nil
} }
func (ic *ContainerEngine) NetworkCreate(ctx context.Context, network types.Network) (*types.Network, error) { func (ic *ContainerEngine) NetworkCreate(ctx context.Context, network types.Network, createOptions *types.NetworkCreateOptions) (*types.Network, error) {
// TODO (5.0): Stop accepting "pasta" as value here // TODO (5.0): Stop accepting "pasta" as value here
if util.StringInSlice(network.Name, []string{"none", "host", "bridge", "private", "slirp4netns", "container", "ns"}) { if util.StringInSlice(network.Name, []string{"none", "host", "bridge", "private", "slirp4netns", "container", "ns"}) {
return nil, fmt.Errorf("cannot create network with name %q because it conflicts with a valid network mode", network.Name) return nil, fmt.Errorf("cannot create network with name %q because it conflicts with a valid network mode", network.Name)
} }
network, err := ic.Libpod.Network().NetworkCreate(network, nil) network, err := ic.Libpod.Network().NetworkCreate(network, createOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -122,12 +122,16 @@ func (ic *ContainerEngine) PlayKube(ctx context.Context, body io.Reader, options
// when no network options are specified, create a common network for all the pods // when no network options are specified, create a common network for all the pods
if len(options.Networks) == 0 { if len(options.Networks) == 0 {
_, err := ic.NetworkCreate( _, err := ic.NetworkCreate(
ctx, nettypes.Network{ ctx,
nettypes.Network{
Name: kubeDefaultNetwork, Name: kubeDefaultNetwork,
DNSEnabled: true, DNSEnabled: true,
}, },
&nettypes.NetworkCreateOptions{
IgnoreIfExists: true,
},
) )
if err != nil && !errors.Is(err, nettypes.ErrNetworkExists) { if err != nil {
return nil, err return nil, err
} }
} }

View File

@ -66,8 +66,12 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o
return reports, nil return reports, nil
} }
func (ic *ContainerEngine) NetworkCreate(ctx context.Context, net types.Network) (*types.Network, error) { func (ic *ContainerEngine) NetworkCreate(ctx context.Context, net types.Network, createOptions *types.NetworkCreateOptions) (*types.Network, error) {
net, err := network.Create(ic.ClientCtx, &net) options := new(network.ExtraCreateOptions)
if createOptions != nil {
options = options.WithIgnoreIfExists(createOptions.IgnoreIfExists)
}
net, err := network.CreateWithOptions(ic.ClientCtx, &net, options)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -452,4 +452,33 @@ var _ = Describe("Podman network create", func() {
Expect(nc).To(Exit(125)) Expect(nc).To(Exit(125))
Expect(nc.ErrorToString()).To(Equal("Error: cannot set more ranges than subnets")) Expect(nc.ErrorToString()).To(Equal("Error: cannot set more ranges than subnets"))
}) })
It("podman network create same name - fail", func() {
name := "same-name-" + stringid.GenerateRandomID()
networkCreateCommand := []string{"network", "create", name}
nc := podmanTest.Podman(networkCreateCommand)
nc.WaitWithDefaultTimeout()
defer podmanTest.removeNetwork(name)
Expect(nc).To(Exit(0))
Expect(nc.OutputToString()).To(Equal(name))
nc = podmanTest.Podman(networkCreateCommand)
nc.WaitWithDefaultTimeout()
Expect(nc).To(Exit(125))
})
It("podman network create same name - succeed with ignore", func() {
name := "same-name-" + stringid.GenerateRandomID()
networkCreateCommand := []string{"network", "create", "--ignore", name}
nc := podmanTest.Podman(networkCreateCommand)
nc.WaitWithDefaultTimeout()
defer podmanTest.removeNetwork(name)
Expect(nc).To(Exit(0))
Expect(nc.OutputToString()).To(Equal(name))
nc = podmanTest.Podman(networkCreateCommand)
nc.WaitWithDefaultTimeout()
Expect(nc).To(Exit(0))
Expect(nc.OutputToString()).To(Equal(name))
})
}) })