mirror of
https://github.com/containers/podman.git
synced 2026-07-10 07:15:05 -04:00
This was implemented by containers/netavark #1369; this commit completes the process by wiring it into Podman. We now respect the CLI order for configured networks - if a user passes `--net net1,net2` we guarantee that net1 will be configured before net2. For containers created before this patch, we don't retain enough information to configure networks in CLI order, so we use alphabetical order instead to still guarantee consistency. No breaking API changes have been made, but we do add a new field to supplement the existing map to (optionally) provide ordering information. The Podman CLI will always pass this. Existing applications that do not will, again, receive] deterministic ordering based on an alphabetical sort of network names. This requires the latest version of Netavark to work properly. Signed-off-by: Matthew Heon <matthew.heon@pm.me>
462 lines
15 KiB
Go
462 lines
15 KiB
Go
// Copyright 2018 go-dockerclient authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package docker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
)
|
|
|
|
// PluginPrivilege represents a privilege for a plugin.
|
|
type PluginPrivilege struct {
|
|
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
|
|
Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"`
|
|
Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"`
|
|
}
|
|
|
|
// InstallPluginOptions specify parameters to the InstallPlugins function.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type InstallPluginOptions struct {
|
|
Remote string
|
|
Name string
|
|
Plugins []PluginPrivilege `qs:"-"`
|
|
|
|
Auth AuthConfiguration
|
|
|
|
Context context.Context
|
|
}
|
|
|
|
// InstallPlugins installs a plugin or returns an error in case of failure.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) InstallPlugins(opts InstallPluginOptions) error {
|
|
headers, err := headersWithAuth(opts.Auth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
path := "/plugins/pull?" + queryString(opts)
|
|
resp, err := c.do(http.MethodPost, path, doOptions{
|
|
data: opts.Plugins,
|
|
context: opts.Context,
|
|
headers: headers,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
// PullPlugin streams back the progress of the pull, we must consume the whole body
|
|
// otherwise the pull will be canceled on the engine.
|
|
if _, err := io.ReadAll(resp.Body); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// PluginSettings stores plugin settings.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginSettings struct {
|
|
Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"`
|
|
Args []string `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"`
|
|
Devices []string `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"`
|
|
}
|
|
|
|
// PluginInterface stores plugin interface.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginInterface struct {
|
|
Types []string `json:"Types,omitempty" yaml:"Types,omitempty" toml:"Types,omitempty"`
|
|
Socket string `json:"Socket,omitempty" yaml:"Socket,omitempty" toml:"Socket,omitempty"`
|
|
}
|
|
|
|
// PluginNetwork stores plugin network type.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginNetwork struct {
|
|
Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"`
|
|
}
|
|
|
|
// PluginLinux stores plugin linux setting.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginLinux struct {
|
|
Capabilities []string `json:"Capabilities,omitempty" yaml:"Capabilities,omitempty" toml:"Capabilities,omitempty"`
|
|
AllowAllDevices bool `json:"AllowAllDevices,omitempty" yaml:"AllowAllDevices,omitempty" toml:"AllowAllDevices,omitempty"`
|
|
Devices []PluginLinuxDevices `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"`
|
|
}
|
|
|
|
// PluginLinuxDevices stores plugin linux device setting.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginLinuxDevices struct {
|
|
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
|
|
Description string `json:"Documentation,omitempty" yaml:"Documentation,omitempty" toml:"Documentation,omitempty"`
|
|
Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"`
|
|
Path string `json:"Path,omitempty" yaml:"Path,omitempty" toml:"Path,omitempty"`
|
|
}
|
|
|
|
// PluginEnv stores plugin environment.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginEnv struct {
|
|
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
|
|
Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"`
|
|
Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"`
|
|
Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"`
|
|
}
|
|
|
|
// PluginArgs stores plugin arguments.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginArgs struct {
|
|
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
|
|
Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"`
|
|
Settable []string `json:"Settable,omitempty" yaml:"Settable,omitempty" toml:"Settable,omitempty"`
|
|
Value []string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"`
|
|
}
|
|
|
|
// PluginUser stores plugin user.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginUser struct {
|
|
UID int32 `json:"UID,omitempty" yaml:"UID,omitempty" toml:"UID,omitempty"`
|
|
GID int32 `json:"GID,omitempty" yaml:"GID,omitempty" toml:"GID,omitempty"`
|
|
}
|
|
|
|
// PluginConfig stores plugin config.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginConfig struct {
|
|
Description string `json:"Description,omitempty" yaml:"Description,omitempty" toml:"Description,omitempty"`
|
|
Documentation string
|
|
Interface PluginInterface `json:"Interface" yaml:"Interface,omitempty" toml:"Interface,omitempty"`
|
|
Entrypoint []string `json:"Entrypoint,omitempty" yaml:"Entrypoint,omitempty" toml:"Entrypoint,omitempty"`
|
|
WorkDir string `json:"WorkDir,omitempty" yaml:"WorkDir,omitempty" toml:"WorkDir,omitempty"`
|
|
User PluginUser `json:"User" yaml:"User,omitempty" toml:"User,omitempty"`
|
|
Network PluginNetwork `json:"Network" yaml:"Network,omitempty" toml:"Network,omitempty"`
|
|
Linux PluginLinux `json:"Linux" yaml:"Linux,omitempty" toml:"Linux,omitempty"`
|
|
PropagatedMount string `json:"PropagatedMount,omitempty" yaml:"PropagatedMount,omitempty" toml:"PropagatedMount,omitempty"`
|
|
Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"`
|
|
Env []PluginEnv `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"`
|
|
Args PluginArgs `json:"Args" yaml:"Args,omitempty" toml:"Args,omitempty"`
|
|
}
|
|
|
|
// PluginDetail specify results from the ListPlugins function.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PluginDetail struct {
|
|
ID string `json:"Id,omitempty" yaml:"Id,omitempty" toml:"Id,omitempty"`
|
|
Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"`
|
|
Tag string `json:"Tag,omitempty" yaml:"Tag,omitempty" toml:"Tag,omitempty"`
|
|
Active bool `json:"Enabled,omitempty" yaml:"Active,omitempty" toml:"Active,omitempty"`
|
|
Settings PluginSettings `json:"Settings" yaml:"Settings,omitempty" toml:"Settings,omitempty"`
|
|
Config PluginConfig `json:"Config" yaml:"Config,omitempty" toml:"Config,omitempty"`
|
|
}
|
|
|
|
// ListPlugins returns pluginDetails or an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) ListPlugins(ctx context.Context) ([]PluginDetail, error) {
|
|
resp, err := c.do(http.MethodGet, "/plugins", doOptions{
|
|
context: ctx,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
pluginDetails := make([]PluginDetail, 0)
|
|
if err := json.NewDecoder(resp.Body).Decode(&pluginDetails); err != nil {
|
|
return nil, err
|
|
}
|
|
return pluginDetails, nil
|
|
}
|
|
|
|
// ListFilteredPluginsOptions specify parameters to the ListFilteredPlugins function.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type ListFilteredPluginsOptions struct {
|
|
Filters map[string][]string
|
|
Context context.Context
|
|
}
|
|
|
|
// ListFilteredPlugins returns pluginDetails or an error.
|
|
//
|
|
// See https://goo.gl/rmdmWg for more details.
|
|
func (c *Client) ListFilteredPlugins(opts ListFilteredPluginsOptions) ([]PluginDetail, error) {
|
|
path := "/plugins/json?" + queryString(opts)
|
|
resp, err := c.do(http.MethodGet, path, doOptions{
|
|
context: opts.Context,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
pluginDetails := make([]PluginDetail, 0)
|
|
if err := json.NewDecoder(resp.Body).Decode(&pluginDetails); err != nil {
|
|
return nil, err
|
|
}
|
|
return pluginDetails, nil
|
|
}
|
|
|
|
// GetPluginPrivileges returns pluginPrivileges or an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) GetPluginPrivileges(remote string, ctx context.Context) ([]PluginPrivilege, error) {
|
|
return c.GetPluginPrivilegesWithOptions(
|
|
GetPluginPrivilegesOptions{
|
|
Remote: remote,
|
|
Context: ctx,
|
|
})
|
|
}
|
|
|
|
// GetPluginPrivilegesOptions specify parameters to the GetPluginPrivilegesWithOptions function.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type GetPluginPrivilegesOptions struct {
|
|
Remote string
|
|
Auth AuthConfiguration
|
|
Context context.Context
|
|
}
|
|
|
|
// GetPluginPrivilegesWithOptions returns pluginPrivileges or an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) GetPluginPrivilegesWithOptions(opts GetPluginPrivilegesOptions) ([]PluginPrivilege, error) {
|
|
headers, err := headersWithAuth(opts.Auth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
path := "/plugins/privileges?" + queryString(opts)
|
|
resp, err := c.do(http.MethodGet, path, doOptions{
|
|
context: opts.Context,
|
|
headers: headers,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
var pluginPrivileges []PluginPrivilege
|
|
if err := json.NewDecoder(resp.Body).Decode(&pluginPrivileges); err != nil {
|
|
return nil, err
|
|
}
|
|
return pluginPrivileges, nil
|
|
}
|
|
|
|
// InspectPlugins returns a pluginDetail or an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) InspectPlugins(name string, ctx context.Context) (*PluginDetail, error) {
|
|
resp, err := c.do(http.MethodGet, "/plugins/"+name+"/json", doOptions{
|
|
context: ctx,
|
|
})
|
|
if err != nil {
|
|
var e *Error
|
|
if errors.As(err, &e) && e.Status == http.StatusNotFound {
|
|
return nil, &NoSuchPlugin{ID: name}
|
|
}
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
var pluginDetail PluginDetail
|
|
if err := json.NewDecoder(resp.Body).Decode(&pluginDetail); err != nil {
|
|
return nil, err
|
|
}
|
|
return &pluginDetail, nil
|
|
}
|
|
|
|
// RemovePluginOptions specify parameters to the RemovePlugin function.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type RemovePluginOptions struct {
|
|
// The Name of the plugin.
|
|
Name string `qs:"-"`
|
|
|
|
Force bool `qs:"force"`
|
|
Context context.Context
|
|
}
|
|
|
|
// RemovePlugin returns a PluginDetail or an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) RemovePlugin(opts RemovePluginOptions) (*PluginDetail, error) {
|
|
path := "/plugins/" + opts.Name + "?" + queryString(opts)
|
|
resp, err := c.do(http.MethodDelete, path, doOptions{context: opts.Context})
|
|
if err != nil {
|
|
var e *Error
|
|
if errors.As(err, &e) && e.Status == http.StatusNotFound {
|
|
return nil, &NoSuchPlugin{ID: opts.Name}
|
|
}
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(body) == 0 {
|
|
// Seems like newer docker versions won't return the plugindetail after removal
|
|
return nil, nil
|
|
}
|
|
|
|
var pluginDetail PluginDetail
|
|
if err := json.Unmarshal(body, &pluginDetail); err != nil {
|
|
return nil, err
|
|
}
|
|
return &pluginDetail, nil
|
|
}
|
|
|
|
// EnablePluginOptions specify parameters to the EnablePlugin function.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type EnablePluginOptions struct {
|
|
// The Name of the plugin.
|
|
Name string `qs:"-"`
|
|
Timeout int64 `qs:"timeout"`
|
|
|
|
Context context.Context
|
|
}
|
|
|
|
// EnablePlugin enables plugin that opts point or returns an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) EnablePlugin(opts EnablePluginOptions) error {
|
|
path := "/plugins/" + opts.Name + "/enable?" + queryString(opts)
|
|
resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// DisablePluginOptions specify parameters to the DisablePlugin function.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type DisablePluginOptions struct {
|
|
// The Name of the plugin.
|
|
Name string `qs:"-"`
|
|
|
|
Context context.Context
|
|
}
|
|
|
|
// DisablePlugin disables plugin that opts point or returns an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) DisablePlugin(opts DisablePluginOptions) error {
|
|
path := "/plugins/" + opts.Name + "/disable"
|
|
resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// CreatePluginOptions specify parameters to the CreatePlugin function.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type CreatePluginOptions struct {
|
|
// The Name of the plugin.
|
|
Name string `qs:"name"`
|
|
// Path to tar containing plugin
|
|
Path string `qs:"-"`
|
|
|
|
Context context.Context
|
|
}
|
|
|
|
// CreatePlugin creates plugin that opts point or returns an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) CreatePlugin(opts CreatePluginOptions) (string, error) {
|
|
path := "/plugins/create?" + queryString(opts)
|
|
resp, err := c.do(http.MethodPost, path, doOptions{
|
|
data: opts.Path,
|
|
context: opts.Context,
|
|
})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer resp.Body.Close()
|
|
containerNameBytes, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(containerNameBytes), nil
|
|
}
|
|
|
|
// PushPluginOptions specify parameters to PushPlugin function.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type PushPluginOptions struct {
|
|
// The Name of the plugin.
|
|
Name string
|
|
|
|
Context context.Context
|
|
}
|
|
|
|
// PushPlugin pushes plugin that opts point or returns an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) PushPlugin(opts PushPluginOptions) error {
|
|
path := "/plugins/" + opts.Name + "/push"
|
|
resp, err := c.do(http.MethodPost, path, doOptions{context: opts.Context})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// ConfigurePluginOptions specify parameters to the ConfigurePlugin
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
type ConfigurePluginOptions struct {
|
|
// The Name of the plugin.
|
|
Name string `qs:"name"`
|
|
Envs []string
|
|
|
|
Context context.Context
|
|
}
|
|
|
|
// ConfigurePlugin configures plugin that opts point or returns an error.
|
|
//
|
|
// See https://goo.gl/C4t7Tz for more details.
|
|
func (c *Client) ConfigurePlugin(opts ConfigurePluginOptions) error {
|
|
path := "/plugins/" + opts.Name + "/set"
|
|
resp, err := c.do(http.MethodPost, path, doOptions{
|
|
data: opts.Envs,
|
|
context: opts.Context,
|
|
})
|
|
if err != nil {
|
|
var e *Error
|
|
if errors.As(err, &e) && e.Status == http.StatusNotFound {
|
|
return &NoSuchPlugin{ID: opts.Name}
|
|
}
|
|
return err
|
|
}
|
|
resp.Body.Close()
|
|
return nil
|
|
}
|
|
|
|
// NoSuchPlugin is the error returned when a given plugin does not exist.
|
|
type NoSuchPlugin struct {
|
|
ID string
|
|
Err error
|
|
}
|
|
|
|
func (err *NoSuchPlugin) Error() string {
|
|
if err.Err != nil {
|
|
return err.Err.Error()
|
|
}
|
|
return "No such plugin: " + err.ID
|
|
}
|