2020-01-27 17:37:01 -08:00
|
|
|
/*
|
|
|
|
|
Copyright 2019 The Kubernetes Authors.
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or impliep.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-01-27 17:38:43 -08:00
|
|
|
package podman
|
2020-01-27 17:37:01 -08:00
|
|
|
|
|
|
|
|
import (
|
2020-01-30 13:12:45 -08:00
|
|
|
"encoding/json"
|
2020-01-27 17:37:01 -08:00
|
|
|
"fmt"
|
|
|
|
|
"net"
|
2020-01-30 15:24:21 -08:00
|
|
|
"os"
|
2020-02-04 15:50:16 -08:00
|
|
|
"path/filepath"
|
2020-01-30 13:12:45 -08:00
|
|
|
"strconv"
|
2020-01-27 17:37:01 -08:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"sigs.k8s.io/kind/pkg/cluster/nodes"
|
2020-01-30 15:24:21 -08:00
|
|
|
"sigs.k8s.io/kind/pkg/cluster/nodeutils"
|
2020-01-27 17:37:01 -08:00
|
|
|
"sigs.k8s.io/kind/pkg/errors"
|
|
|
|
|
"sigs.k8s.io/kind/pkg/exec"
|
|
|
|
|
"sigs.k8s.io/kind/pkg/log"
|
|
|
|
|
|
2020-08-25 22:23:44 -07:00
|
|
|
"sigs.k8s.io/kind/pkg/cluster/internal/providers"
|
|
|
|
|
"sigs.k8s.io/kind/pkg/cluster/internal/providers/common"
|
2020-01-27 17:37:01 -08:00
|
|
|
"sigs.k8s.io/kind/pkg/internal/apis/config"
|
|
|
|
|
"sigs.k8s.io/kind/pkg/internal/cli"
|
2021-11-16 15:43:29 -08:00
|
|
|
"sigs.k8s.io/kind/pkg/internal/sets"
|
2021-11-16 16:00:10 -08:00
|
|
|
"sigs.k8s.io/kind/pkg/internal/version"
|
2020-01-27 17:37:01 -08:00
|
|
|
)
|
|
|
|
|
|
2020-01-27 17:38:43 -08:00
|
|
|
// NewProvider returns a new provider based on executing `podman ...`
|
2020-08-25 22:23:44 -07:00
|
|
|
func NewProvider(logger log.Logger) providers.Provider {
|
2020-02-19 15:06:46 -08:00
|
|
|
logger.Warn("enabling experimental podman provider")
|
2020-08-25 22:23:44 -07:00
|
|
|
return &provider{
|
2020-01-27 17:37:01 -08:00
|
|
|
logger: logger,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Provider implements provider.Provider
|
|
|
|
|
// see NewProvider
|
2020-08-25 22:23:44 -07:00
|
|
|
type provider struct {
|
2020-01-27 17:37:01 -08:00
|
|
|
logger log.Logger
|
2021-03-15 15:05:01 +09:00
|
|
|
info *providers.ProviderInfo
|
2020-01-27 17:37:01 -08:00
|
|
|
}
|
|
|
|
|
|
2020-08-25 22:23:44 -07:00
|
|
|
// String implements fmt.Stringer
|
|
|
|
|
// NOTE: the value of this should not currently be relied upon for anything!
|
|
|
|
|
// This is only used for setting the Node's providerID
|
|
|
|
|
func (p *provider) String() string {
|
|
|
|
|
return "podman"
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-27 17:37:01 -08:00
|
|
|
// Provision is part of the providers.Provider interface
|
2020-08-25 22:23:44 -07:00
|
|
|
func (p *provider) Provision(status *cli.Status, cfg *config.Cluster) (err error) {
|
2020-01-29 14:05:38 -08:00
|
|
|
if err := ensureMinVersion(); err != nil {
|
2020-01-27 21:35:34 -08:00
|
|
|
return err
|
|
|
|
|
}
|
2020-01-29 14:05:38 -08:00
|
|
|
|
2020-01-27 17:37:01 -08:00
|
|
|
// TODO: validate cfg
|
|
|
|
|
// ensure node images are pulled before actually provisioning
|
|
|
|
|
if err := ensureNodeImages(p.logger, status, cfg); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-06 22:03:15 -05:00
|
|
|
// ensure the pre-requisite network exists
|
2020-11-22 12:54:46 +01:00
|
|
|
networkName := fixedNetworkName
|
|
|
|
|
if n := os.Getenv("KIND_EXPERIMENTAL_PODMAN_NETWORK"); n != "" {
|
|
|
|
|
p.logger.Warn("WARNING: Overriding podman network due to KIND_EXPERIMENTAL_PODMAN_NETWORK")
|
|
|
|
|
p.logger.Warn("WARNING: Here be dragons! This is not supported currently.")
|
|
|
|
|
networkName = n
|
|
|
|
|
}
|
|
|
|
|
if err := ensureNetwork(networkName); err != nil {
|
|
|
|
|
return errors.Wrap(err, "failed to ensure podman network")
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-27 17:37:01 -08:00
|
|
|
// actually provision the cluster
|
|
|
|
|
icons := strings.Repeat("📦 ", len(cfg.Nodes))
|
|
|
|
|
status.Start(fmt.Sprintf("Preparing nodes %s", icons))
|
|
|
|
|
defer func() { status.End(err == nil) }()
|
|
|
|
|
|
|
|
|
|
// plan creating the containers
|
2020-11-22 12:54:46 +01:00
|
|
|
createContainerFuncs, err := planCreation(cfg, networkName)
|
2020-01-27 17:37:01 -08:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// actually create nodes
|
|
|
|
|
return errors.UntilErrorConcurrent(createContainerFuncs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListClusters is part of the providers.Provider interface
|
2020-08-25 22:23:44 -07:00
|
|
|
func (p *provider) ListClusters() ([]string, error) {
|
2020-01-27 17:38:43 -08:00
|
|
|
cmd := exec.Command("podman",
|
2020-01-27 17:37:01 -08:00
|
|
|
"ps",
|
2020-03-21 09:26:49 -04:00
|
|
|
"-a", // show stopped nodes
|
2020-01-27 17:37:01 -08:00
|
|
|
// filter for nodes with the cluster label
|
|
|
|
|
"--filter", "label="+clusterLabelKey,
|
|
|
|
|
// format to include the cluster name
|
2020-02-04 13:20:07 -08:00
|
|
|
"--format", fmt.Sprintf(`{{index .Labels "%s"}}`, clusterLabelKey),
|
2020-01-27 17:37:01 -08:00
|
|
|
)
|
|
|
|
|
lines, err := exec.OutputLines(cmd)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "failed to list clusters")
|
|
|
|
|
}
|
|
|
|
|
return sets.NewString(lines...).List(), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListNodes is part of the providers.Provider interface
|
2020-08-25 22:23:44 -07:00
|
|
|
func (p *provider) ListNodes(cluster string) ([]nodes.Node, error) {
|
2020-01-27 17:38:43 -08:00
|
|
|
cmd := exec.Command("podman",
|
2020-01-27 17:37:01 -08:00
|
|
|
"ps",
|
2020-03-21 09:26:49 -04:00
|
|
|
"-a", // show stopped nodes
|
2020-01-27 17:37:01 -08:00
|
|
|
// filter for nodes with the cluster label
|
|
|
|
|
"--filter", fmt.Sprintf("label=%s=%s", clusterLabelKey, cluster),
|
|
|
|
|
// format to include the cluster name
|
|
|
|
|
"--format", `{{.Names}}`,
|
|
|
|
|
)
|
|
|
|
|
lines, err := exec.OutputLines(cmd)
|
|
|
|
|
if err != nil {
|
2021-12-30 17:39:21 +08:00
|
|
|
return nil, errors.Wrap(err, "failed to list nodes")
|
2020-01-27 17:37:01 -08:00
|
|
|
}
|
|
|
|
|
// convert names to node handles
|
|
|
|
|
ret := make([]nodes.Node, 0, len(lines))
|
|
|
|
|
for _, name := range lines {
|
|
|
|
|
ret = append(ret, p.node(name))
|
|
|
|
|
}
|
|
|
|
|
return ret, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DeleteNodes is part of the providers.Provider interface
|
2020-08-25 22:23:44 -07:00
|
|
|
func (p *provider) DeleteNodes(n []nodes.Node) error {
|
2020-01-27 17:37:01 -08:00
|
|
|
if len(n) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-01-27 17:38:43 -08:00
|
|
|
const command = "podman"
|
2020-01-27 17:37:01 -08:00
|
|
|
args := make([]string, 0, len(n)+3) // allocate once
|
|
|
|
|
args = append(args,
|
|
|
|
|
"rm",
|
|
|
|
|
"-f", // force the container to be delete now
|
|
|
|
|
"-v", // delete volumes
|
|
|
|
|
)
|
|
|
|
|
for _, node := range n {
|
|
|
|
|
args = append(args, node.String())
|
|
|
|
|
}
|
|
|
|
|
if err := exec.Command(command, args...).Run(); err != nil {
|
|
|
|
|
return errors.Wrap(err, "failed to delete nodes")
|
|
|
|
|
}
|
2020-05-14 01:40:23 -07:00
|
|
|
var nodeVolumes []string
|
|
|
|
|
for _, node := range n {
|
2020-08-05 14:16:54 -07:00
|
|
|
volumes, err := getVolumes(node.String())
|
2020-05-14 01:40:23 -07:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2020-08-05 14:16:54 -07:00
|
|
|
nodeVolumes = append(nodeVolumes, volumes...)
|
2020-05-14 01:40:23 -07:00
|
|
|
}
|
2022-03-04 13:28:31 -08:00
|
|
|
if len(nodeVolumes) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-05-14 01:40:23 -07:00
|
|
|
return deleteVolumes(nodeVolumes)
|
2020-01-27 17:37:01 -08:00
|
|
|
}
|
|
|
|
|
|
2024-11-06 10:01:38 +08:00
|
|
|
// getHostIPOrDefault defaults HostIP to localhost if is not set
|
|
|
|
|
// xref: https://github.com/kubernetes-sigs/kind/issues/3777
|
|
|
|
|
func getHostIPOrDefault(hostIP string) string {
|
|
|
|
|
if hostIP == "" {
|
|
|
|
|
return "127.0.0.1"
|
|
|
|
|
}
|
|
|
|
|
return hostIP
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-27 17:37:01 -08:00
|
|
|
// GetAPIServerEndpoint is part of the providers.Provider interface
|
2020-08-25 22:23:44 -07:00
|
|
|
func (p *provider) GetAPIServerEndpoint(cluster string) (string, error) {
|
2020-01-27 17:37:01 -08:00
|
|
|
// locate the node that hosts this
|
|
|
|
|
allNodes, err := p.ListNodes(cluster)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "failed to list nodes")
|
|
|
|
|
}
|
|
|
|
|
n, err := nodeutils.APIServerEndpointNode(allNodes)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "failed to get api server endpoint")
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-01 15:37:01 +01:00
|
|
|
// TODO: get rid of this once podman settles on how to get the port mapping using podman inspect
|
|
|
|
|
// This is only used to get the Kubeconfig server field
|
|
|
|
|
v, err := getPodmanVersion()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "failed to check podman version")
|
|
|
|
|
}
|
2021-03-19 22:57:52 +01:00
|
|
|
// podman inspect was broken between 2.2.0 and 3.0.0
|
|
|
|
|
// https://github.com/containers/podman/issues/8444
|
|
|
|
|
if v.AtLeast(version.MustParseSemantic("2.2.0")) &&
|
|
|
|
|
v.LessThan(version.MustParseSemantic("3.0.0")) {
|
|
|
|
|
p.logger.Warnf("WARNING: podman version %s not fully supported, please use versions 3.0.0+")
|
|
|
|
|
|
2020-12-01 15:37:01 +01:00
|
|
|
cmd := exec.Command(
|
|
|
|
|
"podman", "inspect",
|
|
|
|
|
"--format",
|
2021-03-19 22:57:52 +01:00
|
|
|
"{{range .NetworkSettings.Ports }}{{range .}}{{.HostIP}}/{{.HostPort}}{{end}}{{end}}",
|
2020-12-01 15:37:01 +01:00
|
|
|
n.String(),
|
|
|
|
|
)
|
2021-03-19 22:57:52 +01:00
|
|
|
|
2020-12-01 15:37:01 +01:00
|
|
|
lines, err := exec.OutputLines(cmd)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "failed to get api server port")
|
|
|
|
|
}
|
|
|
|
|
if len(lines) != 1 {
|
|
|
|
|
return "", errors.Errorf("network details should only be one line, got %d lines", len(lines))
|
|
|
|
|
}
|
2021-03-19 22:57:52 +01:00
|
|
|
// output is in the format IP/Port
|
|
|
|
|
parts := strings.Split(strings.TrimSpace(lines[0]), "/")
|
|
|
|
|
if len(parts) != 2 {
|
|
|
|
|
return "", errors.Errorf("network details should be in the format IP/Port, received: %s", parts)
|
2020-12-01 15:37:01 +01:00
|
|
|
}
|
2021-03-19 22:57:52 +01:00
|
|
|
host := parts[0]
|
|
|
|
|
port, err := strconv.Atoi(parts[1])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Errorf("network port not an integer: %v", err)
|
2020-12-01 15:37:01 +01:00
|
|
|
}
|
|
|
|
|
|
2021-03-19 22:57:52 +01:00
|
|
|
return net.JoinHostPort(host, strconv.Itoa(port)), nil
|
2020-12-01 15:37:01 +01:00
|
|
|
}
|
2021-03-19 22:57:52 +01:00
|
|
|
|
2020-01-27 17:37:01 -08:00
|
|
|
cmd := exec.Command(
|
2020-01-27 17:38:43 -08:00
|
|
|
"podman", "inspect",
|
2020-01-30 13:12:45 -08:00
|
|
|
"--format",
|
2021-03-19 22:57:52 +01:00
|
|
|
"{{ json .NetworkSettings.Ports }}",
|
2020-01-27 17:37:01 -08:00
|
|
|
n.String(),
|
|
|
|
|
)
|
|
|
|
|
lines, err := exec.OutputLines(cmd)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "failed to get api server port")
|
|
|
|
|
}
|
|
|
|
|
if len(lines) != 1 {
|
|
|
|
|
return "", errors.Errorf("network details should only be one line, got %d lines", len(lines))
|
|
|
|
|
}
|
2021-03-19 22:57:52 +01:00
|
|
|
|
|
|
|
|
// portMapping19 maps to the standard CNI portmapping capability used in podman 1.9
|
|
|
|
|
// see: https://github.com/containernetworking/cni/blob/spec-v0.4.0/CONVENTIONS.md
|
|
|
|
|
type portMapping19 struct {
|
|
|
|
|
HostPort int32 `json:"hostPort"`
|
|
|
|
|
ContainerPort int32 `json:"containerPort"`
|
|
|
|
|
Protocol string `json:"protocol"`
|
|
|
|
|
HostIP string `json:"hostIP"`
|
2020-01-30 13:12:45 -08:00
|
|
|
}
|
2021-03-19 22:57:52 +01:00
|
|
|
// portMapping20 maps to the podman 2.0 portmap type
|
|
|
|
|
// see: https://github.com/containers/podman/blob/05988fc74fc25f2ad2256d6e011dfb7ad0b9a4eb/libpod/define/container_inspect.go#L134-L143
|
|
|
|
|
type portMapping20 struct {
|
|
|
|
|
HostPort string `json:"HostPort"`
|
|
|
|
|
HostIP string `json:"HostIp"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
portMappings20 := make(map[string][]portMapping20)
|
|
|
|
|
if err := json.Unmarshal([]byte(lines[0]), &portMappings20); err == nil {
|
|
|
|
|
for k, v := range portMappings20 {
|
|
|
|
|
protocol := "tcp"
|
|
|
|
|
parts := strings.Split(k, "/")
|
|
|
|
|
if len(parts) == 2 {
|
|
|
|
|
protocol = strings.ToLower(parts[1])
|
|
|
|
|
}
|
|
|
|
|
containerPort, err := strconv.Atoi(parts[0])
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
for _, pm := range v {
|
|
|
|
|
if containerPort == common.APIServerInternalPort && protocol == "tcp" {
|
2024-11-06 10:01:38 +08:00
|
|
|
return net.JoinHostPort(getHostIPOrDefault(pm.HostIP), pm.HostPort), nil
|
2021-03-19 22:57:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var portMappings19 []portMapping19
|
|
|
|
|
if err := json.Unmarshal([]byte(lines[0]), &portMappings19); err != nil {
|
|
|
|
|
return "", errors.Errorf("invalid network details: %v", err)
|
|
|
|
|
}
|
|
|
|
|
for _, pm := range portMappings19 {
|
|
|
|
|
if pm.ContainerPort == common.APIServerInternalPort && pm.Protocol == "tcp" {
|
2024-11-06 10:01:38 +08:00
|
|
|
return net.JoinHostPort(getHostIPOrDefault(pm.HostIP), strconv.Itoa(int(pm.HostPort))), nil
|
2021-03-19 22:57:52 +01:00
|
|
|
}
|
2020-01-27 17:37:01 -08:00
|
|
|
}
|
|
|
|
|
|
2021-03-19 22:57:52 +01:00
|
|
|
return "", errors.Errorf("failed to get api server port")
|
2020-01-27 17:37:01 -08:00
|
|
|
}
|
|
|
|
|
|
2020-04-24 00:11:30 -07:00
|
|
|
// GetAPIServerInternalEndpoint is part of the providers.Provider interface
|
2020-08-25 22:23:44 -07:00
|
|
|
func (p *provider) GetAPIServerInternalEndpoint(cluster string) (string, error) {
|
2020-04-24 00:11:30 -07:00
|
|
|
// locate the node that hosts this
|
|
|
|
|
allNodes, err := p.ListNodes(cluster)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "failed to list nodes")
|
|
|
|
|
}
|
|
|
|
|
n, err := nodeutils.APIServerEndpointNode(allNodes)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "", errors.Wrap(err, "failed to get apiserver endpoint")
|
|
|
|
|
}
|
2020-11-22 12:54:46 +01:00
|
|
|
// NOTE: we're using the nodes's hostnames which are their names
|
|
|
|
|
return net.JoinHostPort(n.String(), fmt.Sprintf("%d", common.APIServerInternalPort)), nil
|
2020-04-24 00:11:30 -07:00
|
|
|
}
|
|
|
|
|
|
2020-01-27 17:37:01 -08:00
|
|
|
// node returns a new node handle for this provider
|
2020-08-25 22:23:44 -07:00
|
|
|
func (p *provider) node(name string) nodes.Node {
|
2020-01-27 17:37:01 -08:00
|
|
|
return &node{
|
|
|
|
|
name: name,
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-02-04 15:50:16 -08:00
|
|
|
|
|
|
|
|
// CollectLogs will populate dir with cluster logs and other debug files
|
2020-08-25 22:23:44 -07:00
|
|
|
func (p *provider) CollectLogs(dir string, nodes []nodes.Node) error {
|
2020-02-04 15:50:16 -08:00
|
|
|
execToPathFn := func(cmd exec.Cmd, path string) func() error {
|
|
|
|
|
return func() error {
|
2020-02-05 17:15:19 -08:00
|
|
|
f, err := common.FileOnHost(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
defer f.Close()
|
|
|
|
|
return cmd.SetStdout(f).SetStderr(f).Run()
|
2020-02-04 15:50:16 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// construct a slice of methods to collect logs
|
|
|
|
|
fns := []func() error{
|
|
|
|
|
// record info about the host podman
|
|
|
|
|
execToPathFn(
|
|
|
|
|
exec.Command("podman", "info"),
|
2020-02-05 17:15:19 -08:00
|
|
|
filepath.Join(dir, "podman-info.txt"),
|
2020-02-04 15:50:16 -08:00
|
|
|
),
|
|
|
|
|
}
|
2025-08-04 17:42:25 -07:00
|
|
|
// inspect each node
|
2020-02-04 15:50:16 -08:00
|
|
|
for _, n := range nodes {
|
|
|
|
|
node := n // https://golang.org/doc/faq#closures_and_goroutines
|
|
|
|
|
name := node.String()
|
2020-02-05 17:15:19 -08:00
|
|
|
path := filepath.Join(dir, name)
|
|
|
|
|
fns = append(fns,
|
|
|
|
|
execToPathFn(exec.Command("podman", "inspect", name), filepath.Join(path, "inspect.json")),
|
|
|
|
|
)
|
2020-02-04 15:50:16 -08:00
|
|
|
}
|
|
|
|
|
// run and collect up all errors
|
2025-08-04 17:42:25 -07:00
|
|
|
return errors.AggregateConcurrent(fns)
|
2020-02-04 15:50:16 -08:00
|
|
|
}
|
2020-11-19 03:53:24 +09:00
|
|
|
|
|
|
|
|
// Info returns the provider info.
|
2021-03-15 15:05:01 +09:00
|
|
|
// The info is cached on the first time of the execution.
|
2020-11-19 03:53:24 +09:00
|
|
|
func (p *provider) Info() (*providers.ProviderInfo, error) {
|
2021-03-15 15:05:01 +09:00
|
|
|
if p.info == nil {
|
2021-05-18 16:00:39 +09:00
|
|
|
var err error
|
|
|
|
|
p.info, err = info(p.logger)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return p.info, err
|
|
|
|
|
}
|
2021-03-15 15:05:01 +09:00
|
|
|
}
|
|
|
|
|
return p.info, nil
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 16:00:39 +09:00
|
|
|
// podmanInfo corresponds to `podman info --format 'json`.
|
|
|
|
|
// The structure is different from `docker info --format '{{json .}}'`,
|
|
|
|
|
// and lacks information about the availability of the cgroup controllers.
|
|
|
|
|
type podmanInfo struct {
|
|
|
|
|
Host struct {
|
2021-10-24 20:04:07 +05:30
|
|
|
CgroupVersion string `json:"cgroupVersion,omitempty"` // "v2"
|
|
|
|
|
CgroupControllers []string `json:"cgroupControllers,omitempty"`
|
|
|
|
|
Security struct {
|
2021-05-18 16:00:39 +09:00
|
|
|
Rootless bool `json:"rootless,omitempty"`
|
|
|
|
|
} `json:"security"`
|
|
|
|
|
} `json:"host"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// info detects ProviderInfo by executing `podman info --format json`.
|
|
|
|
|
func info(logger log.Logger) (*providers.ProviderInfo, error) {
|
|
|
|
|
const podman = "podman"
|
|
|
|
|
args := []string{"info", "--format", "json"}
|
|
|
|
|
cmd := exec.Command(podman, args...)
|
|
|
|
|
out, err := exec.Output(cmd)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrapf(err, "failed to get podman info (%s %s): %q",
|
|
|
|
|
podman, strings.Join(args, " "), string(out))
|
|
|
|
|
}
|
|
|
|
|
var pInfo podmanInfo
|
|
|
|
|
if err := json.Unmarshal(out, &pInfo); err != nil {
|
|
|
|
|
return nil, err
|
2020-11-19 03:53:24 +09:00
|
|
|
}
|
2021-10-24 20:04:07 +05:30
|
|
|
stringSliceContains := func(s []string, str string) bool {
|
|
|
|
|
for _, v := range s {
|
|
|
|
|
if v == str {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Since Podman version before v4.0.0 does not gives controller info.
|
|
|
|
|
// We assume all the cgroup controllers to be available.
|
|
|
|
|
// For rootless, this assumption is not always correct,
|
|
|
|
|
// so we print the warning below.
|
|
|
|
|
cgroupSupportsMemoryLimit := true
|
|
|
|
|
cgroupSupportsPidsLimit := true
|
|
|
|
|
cgroupSupportsCPUShares := true
|
|
|
|
|
|
|
|
|
|
v, err := getPodmanVersion()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, errors.Wrap(err, "failed to check podman version")
|
|
|
|
|
}
|
|
|
|
|
// Info for controllers must be available after v4.0.0
|
|
|
|
|
// via https://github.com/containers/podman/pull/10387
|
|
|
|
|
if v.AtLeast(version.MustParseSemantic("4.0.0")) {
|
|
|
|
|
cgroupSupportsMemoryLimit = stringSliceContains(pInfo.Host.CgroupControllers, "memory")
|
|
|
|
|
cgroupSupportsPidsLimit = stringSliceContains(pInfo.Host.CgroupControllers, "pids")
|
|
|
|
|
cgroupSupportsCPUShares = stringSliceContains(pInfo.Host.CgroupControllers, "cpu")
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-18 16:00:39 +09:00
|
|
|
info := &providers.ProviderInfo{
|
2021-10-24 20:04:07 +05:30
|
|
|
Rootless: pInfo.Host.Security.Rootless,
|
|
|
|
|
Cgroup2: pInfo.Host.CgroupVersion == "v2",
|
|
|
|
|
SupportsMemoryLimit: cgroupSupportsMemoryLimit,
|
|
|
|
|
SupportsPidsLimit: cgroupSupportsPidsLimit,
|
|
|
|
|
SupportsCPUShares: cgroupSupportsCPUShares,
|
|
|
|
|
}
|
|
|
|
|
if info.Rootless && !v.AtLeast(version.MustParseSemantic("4.0.0")) {
|
2022-02-04 22:48:54 +01:00
|
|
|
if logger != nil {
|
|
|
|
|
logger.Warn("Cgroup controller detection is not implemented for Podman. " +
|
|
|
|
|
"If you see cgroup-related errors, you might need to set systemd property \"Delegate=yes\", see https://kind.sigs.k8s.io/docs/user/rootless/")
|
|
|
|
|
}
|
2021-05-18 16:00:39 +09:00
|
|
|
}
|
|
|
|
|
return info, nil
|
2020-11-19 03:53:24 +09:00
|
|
|
}
|