refactor: proxmox cloud config

Move Proxmox cluster config to 'cluster' folder.
This commit is contained in:
Serge Logvinov
2023-04-28 16:25:38 +03:00
parent 850dcd455e
commit cc2dc17de2
10 changed files with 183 additions and 144 deletions

View File

@@ -3,11 +3,11 @@
.git/ .git/
**/.gitignore **/.gitignore
# #
bin/
charts/ charts/
docs/ docs/
hack/ hack/
Dockerfile Dockerfile
/proxmox-cloud-controller-manager*
# #
# other # other
*.md *.md

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
# #
/bin/
/charts/proxmox-cloud-controller-manager/values-dev.yaml /charts/proxmox-cloud-controller-manager/values-dev.yaml
/proxmox-cloud-controller-manager* /proxmox-cloud-controller-manager*
/kubeconfig /kubeconfig

View File

@@ -19,6 +19,6 @@ FROM --platform=${TARGETARCH} gcr.io/distroless/static-debian11:nonroot AS relea
LABEL org.opencontainers.image.source https://github.com/sergelogvinov/proxmox-cloud-controller-manager LABEL org.opencontainers.image.source https://github.com/sergelogvinov/proxmox-cloud-controller-manager
ARG TARGETARCH ARG TARGETARCH
COPY --from=builder /src/proxmox-cloud-controller-manager-${TARGETARCH} /proxmox-cloud-controller-manager COPY --from=builder /src/bin/proxmox-cloud-controller-manager-${TARGETARCH} /proxmox-cloud-controller-manager
ENTRYPOINT ["/proxmox-cloud-controller-manager"] ENTRYPOINT ["/proxmox-cloud-controller-manager"]

View File

@@ -48,14 +48,18 @@ help: ## This help menu.
build-all-archs: build-all-archs:
@for arch in $(ARCHS); do $(MAKE) ARCH=$${arch} build ; done @for arch in $(ARCHS); do $(MAKE) ARCH=$${arch} build ; done
.PHONY: clean
clean: ## Clean
rm -rf bin
.PHONY: build .PHONY: build
build: ## Build build: ## Build
CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build $(GO_LDFLAGS) \ CGO_ENABLED=0 GOOS=$(OS) GOARCH=$(ARCH) go build $(GO_LDFLAGS) \
-o proxmox-cloud-controller-manager-$(ARCH) ./cmd/proxmox-cloud-controller-manager -o bin/proxmox-cloud-controller-manager-$(ARCH) ./cmd/proxmox-cloud-controller-manager
.PHONY: run .PHONY: run
run: build run: build
./proxmox-cloud-controller-manager-$(ARCH) --v=5 --kubeconfig=kubeconfig --cloud-config=proxmox-config.yaml --controllers=cloud-node,cloud-node-lifecycle \ ./bin/proxmox-cloud-controller-manager-$(ARCH) --v=5 --kubeconfig=kubeconfig --cloud-config=proxmox-config.yaml --controllers=cloud-node,cloud-node-lifecycle \
--use-service-account-credentials --leader-elect=false --bind-address=127.0.0.1 --use-service-account-credentials --leader-elect=false --bind-address=127.0.0.1
.PHONY: lint .PHONY: lint

85
pkg/cluster/client.go Normal file
View File

@@ -0,0 +1,85 @@
// Package cluster implements the multi-cloud provider interface for Proxmox.
package cluster
import (
"crypto/tls"
"fmt"
"os"
pxapi "github.com/Telmate/proxmox-api-go/proxmox"
)
// Client is a Proxmox client.
type Client struct {
config *ClustersConfig
proxmox map[string]*pxapi.Client
}
// NewClient creates a new Proxmox client.
func NewClient(config *ClustersConfig) (*Client, error) {
clusters := len(config.Clusters)
if clusters > 0 {
proxmox := make(map[string]*pxapi.Client, clusters)
for _, cfg := range config.Clusters {
tlsconf := &tls.Config{InsecureSkipVerify: true}
if !cfg.Insecure {
tlsconf = nil
}
client, err := pxapi.NewClient(cfg.URL, nil, os.Getenv("PM_HTTP_HEADERS"), tlsconf, "", 600)
if err != nil {
return nil, err
}
client.SetAPIToken(cfg.TokenID, cfg.TokenSecret)
if _, err := client.GetVersion(); err != nil {
return nil, fmt.Errorf("failed to initialized proxmox client in cluster %s: %v", cfg.Region, err)
}
proxmox[cfg.Region] = client
}
return &Client{
config: config,
proxmox: proxmox,
}, nil
}
return nil, nil
}
// CheckClusters checks if the Proxmox connection is working.
func (c *Client) CheckClusters() error {
for region, client := range c.proxmox {
if _, err := client.GetVersion(); err != nil {
return fmt.Errorf("failed to initialized proxmox client in region %s, error: %v", region, err)
}
}
return nil
}
// GetProxmoxCluster returns a Proxmox cluster client in a given region.
func (c *Client) GetProxmoxCluster(region string) (*pxapi.Client, error) {
if c.proxmox[region] != nil {
return c.proxmox[region], nil
}
return nil, fmt.Errorf("proxmox cluster %s not found", region)
}
// FindVMByName find a VM by name in all Proxmox clusters.
func (c *Client) FindVMByName(name string) (*pxapi.VmRef, string, error) {
for region, px := range c.proxmox {
vmr, err := px.GetVmRefByName(name)
if err != nil {
continue
}
return vmr, region, nil
}
return nil, "", fmt.Errorf("VM %s not found", name)
}

View File

@@ -0,0 +1,53 @@
package cluster
import (
"fmt"
"io"
"os"
"path/filepath"
yaml "gopkg.in/yaml.v3"
)
// ClustersConfig is proxmox multi-cluster cloud config.
type ClustersConfig struct {
Clusters []struct {
URL string `yaml:"url"`
Insecure bool `yaml:"insecure,omitempty"`
TokenID string `yaml:"token_id,omitempty"`
TokenSecret string `yaml:"token_secret,omitempty"`
Region string `yaml:"region,omitempty"`
} `yaml:"clusters,omitempty"`
}
// ReadCloudConfig reads cloud config from a reader.
func ReadCloudConfig(config io.Reader) (ClustersConfig, error) {
cfg := ClustersConfig{}
if config != nil {
if err := yaml.NewDecoder(config).Decode(&cfg); err != nil {
return ClustersConfig{}, err
}
}
return cfg, nil
}
// ReadCloudConfigFromFile reads cloud config from a file.
func ReadCloudConfigFromFile(file string) (ClustersConfig, error) {
f, err := os.Open(filepath.Clean(file))
if err != nil {
return ClustersConfig{}, fmt.Errorf("error reading %s: %v", file, err)
}
defer f.Close() // nolint: errcheck
cfg := ClustersConfig{}
if f != nil {
if err := yaml.NewDecoder(f).Decode(&cfg); err != nil {
return ClustersConfig{}, err
}
}
return cfg, nil
}

View File

@@ -1,68 +0,0 @@
package proxmox
import (
"crypto/tls"
"os"
pxapi "github.com/Telmate/proxmox-api-go/proxmox"
clientkubernetes "k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
)
type client struct {
config *cloudConfig
proxmox []pxCluster
kclient clientkubernetes.Interface
}
type pxCluster struct {
client *pxapi.Client
region string
}
func newClient(config *cloudConfig) (*client, error) {
clusters := len(config.Clusters)
if clusters > 0 {
proxmox := make([]pxCluster, clusters)
for idx, cfg := range config.Clusters {
tlsconf := &tls.Config{InsecureSkipVerify: true}
if !cfg.Insecure {
tlsconf = nil
}
client, err := pxapi.NewClient(cfg.URL, nil, os.Getenv("PM_HTTP_HEADERS"), tlsconf, "", 600)
if err != nil {
return nil, err
}
client.SetAPIToken(cfg.TokenID, cfg.TokenSecret)
if _, err := client.GetVersion(); err != nil {
klog.Errorf("failed to initialized proxmox client in cluster %s: %v", cfg.Region, err)
return nil, err
}
proxmox[idx] = pxCluster{client: client, region: cfg.Region}
}
return &client{
config: config,
proxmox: proxmox,
}, nil
}
return nil, nil
}
func (c *client) GetProxmoxCluster(region string) (*pxCluster, error) {
for _, px := range c.proxmox {
if px.region == region {
return &px, nil
}
}
return nil, nil
}

View File

@@ -5,6 +5,9 @@ import (
"context" "context"
"io" "io"
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster"
clientkubernetes "k8s.io/client-go/kubernetes"
cloudprovider "k8s.io/cloud-provider" cloudprovider "k8s.io/cloud-provider"
"k8s.io/klog/v2" "k8s.io/klog/v2"
) )
@@ -17,7 +20,8 @@ const (
) )
type cloud struct { type cloud struct {
client *client client *cluster.Client
kclient clientkubernetes.Interface
instancesV2 cloudprovider.InstancesV2 instancesV2 cloudprovider.InstancesV2
ctx context.Context //nolint:containedctx ctx context.Context //nolint:containedctx
@@ -26,7 +30,7 @@ type cloud struct {
func init() { func init() {
cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) { cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
cfg, err := readCloudConfig(config) cfg, err := cluster.ReadCloudConfig(config)
if err != nil { if err != nil {
klog.Errorf("failed to read config: %v", err) klog.Errorf("failed to read config: %v", err)
@@ -37,8 +41,8 @@ func init() {
}) })
} }
func newCloud(config *cloudConfig) (cloudprovider.Interface, error) { func newCloud(config *cluster.ClustersConfig) (cloudprovider.Interface, error) {
client, err := newClient(config) client, err := cluster.NewClient(config)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -55,7 +59,7 @@ func newCloud(config *cloudConfig) (cloudprovider.Interface, error) {
// to perform housekeeping or run custom controllers specific to the cloud provider. // to perform housekeeping or run custom controllers specific to the cloud provider.
// Any tasks started here should be cleaned up when the stop channel closes. // Any tasks started here should be cleaned up when the stop channel closes.
func (c *cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) { func (c *cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder, stop <-chan struct{}) {
c.client.kclient = clientBuilder.ClientOrDie(ServiceAccountName) c.kclient = clientBuilder.ClientOrDie(ServiceAccountName)
klog.Infof("clientset initialized") klog.Infof("clientset initialized")
@@ -63,12 +67,9 @@ func (c *cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder,
c.ctx = ctx c.ctx = ctx
c.stop = cancel c.stop = cancel
for _, px := range c.client.proxmox { err := c.client.CheckClusters()
if _, err := px.client.GetVersion(); err != nil { if err != nil {
klog.Errorf("failed to initialized proxmox client on region %s: %v", px.region, err) klog.Errorf("failed to initialized proxmox client: %v", err)
return
}
} }
// Broadcast the upstream stop signal to all provider-level goroutines // Broadcast the upstream stop signal to all provider-level goroutines

View File

@@ -1,31 +0,0 @@
package proxmox
import (
"io"
yaml "gopkg.in/yaml.v3"
)
type cloudConfig struct {
Clusters []struct {
URL string `yaml:"url"`
Insecure bool `yaml:"insecure,omitempty"`
TokenID string `yaml:"token_id,omitempty"`
TokenSecret string `yaml:"token_secret,omitempty"`
Region string `yaml:"region,omitempty"`
} `yaml:"clusters,omitempty"`
}
func readCloudConfig(config io.Reader) (cloudConfig, error) {
cfg := cloudConfig{}
if config != nil {
if err := yaml.NewDecoder(config).Decode(&cfg); err != nil {
return cloudConfig{}, err
}
}
// klog.V(5).Infof("cloudConfig: %+v", cfg)
return cfg, nil
}

View File

@@ -9,6 +9,8 @@ import (
pxapi "github.com/Telmate/proxmox-api-go/proxmox" pxapi "github.com/Telmate/proxmox-api-go/proxmox"
"github.com/sergelogvinov/proxmox-cloud-controller-manager/pkg/cluster"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
cloudprovider "k8s.io/cloud-provider" cloudprovider "k8s.io/cloud-provider"
cloudproviderapi "k8s.io/cloud-provider/api" cloudproviderapi "k8s.io/cloud-provider/api"
@@ -16,10 +18,10 @@ import (
) )
type instances struct { type instances struct {
c *client c *cluster.Client
} }
func newInstances(client *client) *instances { func newInstances(client *cluster.Client) *instances {
return &instances{ return &instances{
c: client, c: client,
} }
@@ -71,7 +73,7 @@ func (i *instances) InstanceShutdown(_ context.Context, node *v1.Node) (bool, er
return false, err return false, err
} }
vmState, err := px.client.GetVmState(vmRef) vmState, err := px.GetVmState(vmRef)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -93,22 +95,16 @@ func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudpr
var ( var (
vmRef *pxapi.VmRef vmRef *pxapi.VmRef
region string region string
err error
) )
providerID := node.Spec.ProviderID providerID := node.Spec.ProviderID
if providerID == "" { if providerID == "" {
klog.V(4).Infof("instances.InstanceMetadata() - trying to find providerID for node %s", node.Name) klog.V(4).Infof("instances.InstanceMetadata() - trying to find providerID for node %s", node.Name)
for _, px := range i.c.proxmox { vmRef, region, err = i.c.FindVMByName(node.Name)
vm, err := px.client.GetVmRefByName(node.Name) if err != nil {
if err != nil { return nil, fmt.Errorf("instances.InstanceMetadata() - failed to find instance by name %s: %v, skipped", node.Name, err)
continue
}
vmRef = vm
region = px.region
break
} }
} else if !strings.HasPrefix(node.Spec.ProviderID, ProviderName) { } else if !strings.HasPrefix(node.Spec.ProviderID, ProviderName) {
klog.V(4).Infof("instances.InstanceMetadata() node %s has foreign providerID: %s, skipped", node.Name, node.Spec.ProviderID) klog.V(4).Infof("instances.InstanceMetadata() node %s has foreign providerID: %s, skipped", node.Name, node.Spec.ProviderID)
@@ -117,8 +113,6 @@ func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudpr
} }
if vmRef == nil { if vmRef == nil {
var err error
vmRef, region, err = i.getInstance(node) vmRef, region, err = i.getInstance(node)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -128,15 +122,13 @@ func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudpr
addresses := []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: providedIP}} addresses := []v1.NodeAddress{{Type: v1.NodeInternalIP, Address: providedIP}}
addresses = append(addresses, v1.NodeAddress{Type: v1.NodeHostName, Address: node.Name}) addresses = append(addresses, v1.NodeAddress{Type: v1.NodeHostName, Address: node.Name})
providerID = fmt.Sprintf("%s://%s/%d", ProviderName, region, vmRef.VmId())
instanceType, err := i.getInstanceType(vmRef, region) instanceType, err := i.getInstanceType(vmRef, region)
if err != nil { if err != nil {
instanceType = vmRef.GetVmType() instanceType = vmRef.GetVmType()
} }
return &cloudprovider.InstanceMetadata{ return &cloudprovider.InstanceMetadata{
ProviderID: providerID, ProviderID: i.getProviderID(region, vmRef),
NodeAddresses: addresses, NodeAddresses: addresses,
InstanceType: instanceType, InstanceType: instanceType,
Zone: vmRef.Node(), Zone: vmRef.Node(),
@@ -147,6 +139,10 @@ func (i *instances) InstanceMetadata(_ context.Context, node *v1.Node) (*cloudpr
return &cloudprovider.InstanceMetadata{}, nil return &cloudprovider.InstanceMetadata{}, nil
} }
func (i *instances) getProviderID(region string, vmr *pxapi.VmRef) string {
return fmt.Sprintf("%s://%s/%d", ProviderName, region, vmr.VmId())
}
func (i *instances) getInstance(node *v1.Node) (*pxapi.VmRef, string, error) { func (i *instances) getInstance(node *v1.Node) (*pxapi.VmRef, string, error) {
if !strings.HasPrefix(node.Spec.ProviderID, ProviderName) { if !strings.HasPrefix(node.Spec.ProviderID, ProviderName) {
klog.V(4).Infof("instances.getInstance() node %s has foreign providerID: %s, skipped", node.Name, node.Spec.ProviderID) klog.V(4).Infof("instances.getInstance() node %s has foreign providerID: %s, skipped", node.Name, node.Spec.ProviderID)
@@ -154,19 +150,17 @@ func (i *instances) getInstance(node *v1.Node) (*pxapi.VmRef, string, error) {
return nil, "", fmt.Errorf("node %s has foreign providerID: %s", node.Name, node.Spec.ProviderID) return nil, "", fmt.Errorf("node %s has foreign providerID: %s", node.Name, node.Spec.ProviderID)
} }
vmid, region, err := i.parseProviderID(node.Spec.ProviderID) vm, region, err := i.parseProviderID(node.Spec.ProviderID)
if err != nil { if err != nil {
return nil, "", err return nil, "", fmt.Errorf("instances.getInstance() error: %v", err)
} }
vmRef := pxapi.NewVmRef(vmid)
px, err := i.c.GetProxmoxCluster(region) px, err := i.c.GetProxmoxCluster(region)
if err != nil { if err != nil {
return nil, "", err return nil, "", fmt.Errorf("instances.getInstance() error: %v", err)
} }
vmInfo, err := px.client.GetVmInfo(vmRef) vmInfo, err := px.GetVmInfo(vm)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "not found") { if strings.Contains(err.Error(), "not found") {
return nil, "", cloudprovider.InstanceNotFound return nil, "", cloudprovider.InstanceNotFound
@@ -177,7 +171,7 @@ func (i *instances) getInstance(node *v1.Node) (*pxapi.VmRef, string, error) {
klog.V(5).Infof("instances.getInstance() vmInfo %+v", vmInfo) klog.V(5).Infof("instances.getInstance() vmInfo %+v", vmInfo)
return vmRef, region, nil return vm, region, nil
} }
func (i *instances) getInstanceType(vmRef *pxapi.VmRef, region string) (string, error) { func (i *instances) getInstanceType(vmRef *pxapi.VmRef, region string) (string, error) {
@@ -186,7 +180,7 @@ func (i *instances) getInstanceType(vmRef *pxapi.VmRef, region string) (string,
return "", err return "", err
} }
vmInfo, err := px.client.GetVmInfo(vmRef) vmInfo, err := px.GetVmInfo(vmRef)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -198,16 +192,16 @@ func (i *instances) getInstanceType(vmRef *pxapi.VmRef, region string) (string,
var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `://([^/]*)/([^/]+)$`) var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `://([^/]*)/([^/]+)$`)
func (i *instances) parseProviderID(providerID string) (int, string, error) { func (i *instances) parseProviderID(providerID string) (*pxapi.VmRef, string, error) {
matches := providerIDRegexp.FindStringSubmatch(providerID) matches := providerIDRegexp.FindStringSubmatch(providerID)
if len(matches) != 3 { if len(matches) != 3 {
return 0, "", fmt.Errorf("ProviderID \"%s\" didn't match expected format \"%s://region/InstanceID\"", providerID, ProviderName) return nil, "", fmt.Errorf("ProviderID \"%s\" didn't match expected format \"%s://region/InstanceID\"", providerID, ProviderName)
} }
vmID, err := strconv.Atoi(matches[2]) vmID, err := strconv.Atoi(matches[2])
if err != nil { if err != nil {
return 0, "", err return nil, "", err
} }
return vmID, matches[1], nil return pxapi.NewVmRef(vmID), matches[1], nil
} }