diff --git a/config/cloudinit/datasource/proxmox/proxmox.go b/config/cloudinit/datasource/proxmox/proxmox.go new file mode 100644 index 00000000..34da7b80 --- /dev/null +++ b/config/cloudinit/datasource/proxmox/proxmox.go @@ -0,0 +1,113 @@ +package proxmox + +import ( + "fmt" + "io/ioutil" + "os" + "path" + "syscall" + + "github.com/rancher/os/config/cloudinit/datasource" + "github.com/rancher/os/pkg/log" + "github.com/rancher/os/pkg/util" + + "github.com/docker/docker/pkg/mount" +) + +const ( + configDev = "/dev/sr0" + configDevMountPoint = "/media/pve-config" +) + +type Proxmox struct { + root string + readFile func(filename string) ([]byte, error) + lastError error + availabilityChanges bool +} + +func NewDataSource(root string) *Proxmox { + return &Proxmox{root, ioutil.ReadFile, nil, true} +} + +func (pve *Proxmox) IsAvailable() bool { + if pve.root == configDevMountPoint { + pve.lastError = MountConfigDrive() + if pve.lastError != nil { + log.Error(pve.lastError) + pve.availabilityChanges = false + return false + } + defer pve.Finish() + } + + _, pve.lastError = os.Stat(pve.root) + return !os.IsNotExist(pve.lastError) +} + +func (pve *Proxmox) Finish() error { + return UnmountConfigDrive() +} + +func (pve *Proxmox) String() string { + if pve.lastError != nil { + return fmt.Sprintf("%s: %s (lastError: %v)", pve.Type(), pve.root, pve.lastError) + } + return fmt.Sprintf("%s: %s", pve.Type(), pve.root) +} + +func (pve *Proxmox) AvailabilityChanges() bool { + return pve.availabilityChanges +} + +func (pve *Proxmox) ConfigRoot() string { + return pve.root +} + +func (pve *Proxmox) FetchMetadata() (metadata datasource.Metadata, err error) { + return datasource.Metadata{}, nil +} + +func (pve *Proxmox) FetchUserdata() ([]byte, error) { + return pve.tryReadFile(path.Join(pve.root, "user-data")) +} + +func (pve *Proxmox) Type() string { + return "proxmox" +} + +func (pve *Proxmox) tryReadFile(filename string) ([]byte, error) { + if pve.root == configDevMountPoint { + pve.lastError = MountConfigDrive() + if pve.lastError != nil { + log.Error(pve.lastError) + return nil, pve.lastError + } + defer pve.Finish() + } + log.Debugf("Attempting to read from %q\n", filename) + data, err := pve.readFile(filename) + if os.IsNotExist(err) { + err = nil + } + if err != nil { + log.Errorf("ERROR read cloud-config file(%s) - err: %q", filename, err) + } + return data, err +} + +func MountConfigDrive() error { + if err := os.MkdirAll(configDevMountPoint, 700); err != nil { + return err + } + + fsType, err := util.GetFsType(configDev) + if err != nil { + return err + } + return mount.Mount(configDev, configDevMountPoint, fsType, "ro") +} + +func UnmountConfigDrive() error { + return syscall.Unmount(configDevMountPoint, 0) +} diff --git a/config/cloudinit/datasource/proxmox/proxmox_test.go b/config/cloudinit/datasource/proxmox/proxmox_test.go new file mode 100644 index 00000000..86486fca --- /dev/null +++ b/config/cloudinit/datasource/proxmox/proxmox_test.go @@ -0,0 +1,73 @@ +package proxmox + +import "testing" + +func TestFetchUserdata(t *testing.T) { + for _, tt := range []struct { + root string + files test.MockFilesystem + userdata string + }{ + { + root: "/", + files: test.NewMockFilesystem(), + userdata: "", + }, + { + root: "/media/pve-config", + files: test.NewMockFilesystem(test.File{Path: "/media/pve-config/user-data", Contents: "userdata"}), + userdata: "userdata", + }, + } { + pve := Proxmox{tt.root, tt.files.ReadFile, nil, true} + userdata, err := pve.FetchUserdata() + if err != nil { + t.Fatalf("bad error for %+v: want %v, get %q", tt, nil, err) + } + if string(userdata) != tt.userdata { + t.Fatalf("bad userdata for %+v: want %q, got %q", tt, tt.userdata, userdata) + } + } +} + +func TestConfigRoot(t *testing.T) { + for _, tt := range []struct { + root string + configRoot string + }{ + { + root: "/", + configRoot: "/", + }, + { + root: "/media/pve-config", + configRoot: "/media/pve-config", + }, + } { + pve := Proxmox{tt.root, nil, nil, true} + if configRoot := pve.ConfigRoot(); configRoot != tt.configRoot { + t.Fatalf("bad config root for %q: want %q, got %q", tt, tt.configRoot, configRoot) + } + } +} + +func TestNewDataSource(t *testing.T) { + for _, tt := range []struct { + root string + expectRoot string + }{ + { + root: "", + expectRoot: "", + }, + { + root: "/media/pve-config", + expectRoot: "/media/pve-config", + }, + } { + service := NewDataSource(tt.root) + if service.root != tt.expectRoot { + t.Fatalf("bad root (%q): want %q, got %q", tt.root, tt.expectRoot, service.root) + } + } +} diff --git a/pkg/init/cloudinit/cloudinit.go b/pkg/init/cloudinit/cloudinit.go index 2dfad308..2ecf1aa2 100644 --- a/pkg/init/cloudinit/cloudinit.go +++ b/pkg/init/cloudinit/cloudinit.go @@ -36,6 +36,14 @@ func CloudInit(cfg *config.CloudConfig) (*config.CloudConfig, error) { cfg.Rancher.CloudInit.Datasources = append([]string{"exoscale"}, cfg.Rancher.CloudInit.Datasources...) } + proxmox, err := onlyProxmox() + if err != nil { + log.Error(err) + } + if proxmox { + cfg.Rancher.CloudInit.Datasources = append([]string{"proxmox"}, cfg.Rancher.CloutInit.Datasources...) + } + if len(cfg.Rancher.CloudInit.Datasources) == 0 { log.Info("No specific datasources, ignore cloudinit") return cfg, nil @@ -151,3 +159,12 @@ func onlyExoscale() (bool, error) { return strings.HasPrefix(string(f), "Exoscale"), nil } + +func onlyProxmox() (bool, error) { + f, err := ioutil.ReadFile("/sys/class/dmi/id/product_name") + if err != nil { + return false, err + } + + return strings.Contains(string(f), "Proxmox"), nil +}