Skip to content
118 changes: 86 additions & 32 deletions cobblerclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ import (
"bytes"
"errors"
"fmt"
"github.com/go-viper/mapstructure/v2"
"github.com/kolo/xmlrpc"
"io"
"net/http"
"reflect"
"sort"
"strings"

"github.com/go-viper/mapstructure/v2"
"github.com/kolo/xmlrpc"
)

const bodyTypeXML = "text/xml"
Expand Down Expand Up @@ -157,11 +158,15 @@ func (c *Client) Ping() (bool, error) {
}
}

// AutoAddRepos automatically imports any repos server side that are known to the daemon. It is the responsitbility
// of the caller to execute [Client.BackgroundReposync].
func (c *Client) AutoAddRepos() error {
_, err := c.Call("auto_add_repos", c.Token)
return err
// AutoAddRepos automatically imports any repos server-side that are known to the daemon.
// Returns whether the operation succeeded. The caller is responsible for following up
// with [Client.BackgroundReposync].
func (c *Client) AutoAddRepos() (bool, error) {
result, err := c.Call("auto_add_repos", c.Token)
if err != nil {
return false, err
}
return result.(bool), nil
}

// GetAutoinstallTemplates retrieves a list of all templates that are in use by Cobbler.
Expand All @@ -176,10 +181,13 @@ func (c *Client) GetAutoinstallSnippets() error {
return err
}

// IsAutoinstallInUse checks if a given system has reported that it is currently installing.
func (c *Client) IsAutoinstallInUse(name string) error {
_, err := c.Call("is_autoinstall_in_use", name, c.Token)
return err
// IsAutoinstallInUse reports whether the named system is currently installing.
func (c *Client) IsAutoinstallInUse(name string) (bool, error) {
result, err := c.Call("is_autoinstall_in_use", name, c.Token)
if err != nil {
return false, err
}
return result.(bool), nil
}

// GenerateIPxe generates the iPXE (formerly gPXE) configuration data.
Expand All @@ -206,36 +214,82 @@ func (c *Client) GetBlendedData(profile, system string) (map[string]interface{},
return result.(map[string]interface{}), err
}

// RegisterNewSystem registers a new system without a Cobbler token. This is normally called
// during unattended installation by a script.
func (c *Client) RegisterNewSystem(info map[string]interface{}) error {
_, err := c.Call("register_new_system", info, c.Token)
return err
// RegisterNewSystem registers a new system without a Cobbler token. Normally
// called during unattended installation by a script. Returns the backend's
// status code (0 on success).
func (c *Client) RegisterNewSystem(info map[string]interface{}) (int, error) {
result, err := c.Call("register_new_system", info, c.Token)
if err != nil {
return -1, err
}
return convertToInt(result)
}

// RunInstallTriggers runs installation triggers for a given object. This is normally called during
// unattended installation.
func (c *Client) RunInstallTriggers(mode string, objtype string, name string, ip string) error {
_, err := c.Call("run_install_triggers", mode, objtype, name, ip, c.Token)
return err
// RunInstallTriggers runs installation triggers for a given object. Normally
// called during unattended installation. mode is "pre", "post", or "firstboot";
// objtype is "system" or "profile". Returns whether the trigger ran successfully.
func (c *Client) RunInstallTriggers(mode, objtype, name, ip string) (bool, error) {
result, err := c.Call("run_install_triggers", mode, objtype, name, ip, c.Token)
if err != nil {
return false, err
}
return result.(bool), nil
}

// GetReposCompatibleWithProfile returns all repositories that can be potentially assigned to a given profile.
func (c *Client) GetReposCompatibleWithProfile(profile_name string) error {
_, err := c.Call("get_repos_compatible_with_profile", profile_name, c.Token)
return err
// GetReposCompatibleWithProfile returns all repositories that can be assigned
// to the named profile (filtered by arch compatibility with the profile's distro).
func (c *Client) GetReposCompatibleWithProfile(profileName string) ([]map[string]interface{}, error) {
result, err := c.Call("get_repos_compatible_with_profile", profileName, c.Token)
if err != nil {
return nil, err
}
raw, ok := result.([]interface{})
if !ok {
return nil, fmt.Errorf("get_repos_compatible_with_profile returned %T, want list", result)
}
out := make([]map[string]interface{}, 0, len(raw))
for _, v := range raw {
if m, ok := v.(map[string]interface{}); ok {
out = append(out, m)
}
}
return out, nil
}

// FindSystemByDnsName searches for a system with a given DNS name.
func (c *Client) FindSystemByDnsName(dns_name string) error {
_, err := c.Call("find_system_by_dns_name", dns_name)
return err
// FindSystemByDnsName searches for a system by DNS name. Returns the rendered
// system dict or an empty map if not found.
func (c *Client) FindSystemByDnsName(dnsName string) (map[string]interface{}, error) {
result, err := c.Call("find_system_by_dns_name", dnsName)
if err != nil {
return nil, err
}
if m, ok := result.(map[string]interface{}); ok {
return m, nil
}
return map[string]interface{}{}, nil
}

// GetRandomMacFor returns a random MAC address tailored for the given virt_type.
// Valid values per Python signature: "qemu", "kvm", "xenpv", "xenfv", "vmware",
// "vmwarew", "openvz", "auto".
func (c *Client) GetRandomMacFor(virtType string) (string, error) {
if virtType == "" {
virtType = "kvm"
}
result, err := c.Call("get_random_mac", virtType, c.Token)
if err != nil {
return "", err
}
s, ok := result.(string)
if !ok {
return "", fmt.Errorf("get_random_mac returned %T, want string", result)
}
return s, nil
}

// GetRandomMac generates a random MAC address for use with a virtualized system.
func (c *Client) GetRandomMac() error {
_, err := c.Call("get_random_mac")
return err
func (c *Client) GetRandomMac() (string, error) {
return c.GetRandomMacFor("xenpv")
}

type StatusOption string
Expand Down
14 changes: 7 additions & 7 deletions cobblerclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func TestPing(t *testing.T) {
func TestAutoAddRepos(t *testing.T) {
c := createStubHTTPClientSingle(t, "auto-add-repos")

err := c.AutoAddRepos()
_, err := c.AutoAddRepos()
FailOnError(t, err)
}

Expand All @@ -110,7 +110,7 @@ func TestGetAutoinstallSnippets(t *testing.T) {
func TestIsAutoinstallInUse(t *testing.T) {
c := createStubHTTPClientSingle(t, "is-autoinstall-in-use")

err := c.IsAutoinstallInUse("")
_, err := c.IsAutoinstallInUse("")
FailOnError(t, err)
}

Expand Down Expand Up @@ -151,7 +151,7 @@ func TestRegisterNewSystem(t *testing.T) {

c := createStubHTTPClientSingle(t, "register-new-system")

err := c.RegisterNewSystem(
_, err := c.RegisterNewSystem(
map[string]interface{}{
"name": "test",
"profile": "testprof",
Expand All @@ -168,7 +168,7 @@ func TestRegisterNewSystem(t *testing.T) {
func TestRunInstallTriggers(t *testing.T) {
c := createStubHTTPClientSingle(t, "run-install-triggers")

err := c.RunInstallTriggers("", "", "", "")
_, err := c.RunInstallTriggers("", "", "", "")
FailOnError(t, err)
}

Expand All @@ -178,7 +178,7 @@ func TestGetReposCompatibleWithProfile(t *testing.T) {
"get-repos-compatible-with-profile",
)

err := c.GetReposCompatibleWithProfile("testprof")
_, err := c.GetReposCompatibleWithProfile("testprof")
FailOnError(t, err)
}

Expand All @@ -188,14 +188,14 @@ func TestFindSystemByDnsName(t *testing.T) {
"find-system-by-dns-name",
)

err := c.FindSystemByDnsName("testname")
_, err := c.FindSystemByDnsName("testname")
FailOnError(t, err)
}

func TestGetRandomMac(t *testing.T) {
c := createStubHTTPClientSingle(t, "get-random-mac")

err := c.GetRandomMac()
_, err := c.GetRandomMac()
FailOnError(t, err)
}

Expand Down
15 changes: 15 additions & 0 deletions distro.go
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,18 @@ func (c *Client) GetDistroHandle(name string) (string, error) {
res, err := c.Call("get_distro_handle", name, c.Token)
return returnString(res, err)
}

// GetValidDistroBootLoaders retrieves the list of bootloaders that can be assigned to a distro.
func (c *Client) GetValidDistroBootLoaders(distroName string) ([]string, error) {
resultUnmarshalled, err := c.Call("get_valid_distro_boot_loaders", distroName, c.Token)
return returnStringSlice(resultUnmarshalled, err)
}

// GetDistroAsRendered returns the datastructure after it has passed through Cobblers inheritance structure.
func (c *Client) GetDistroAsRendered(name string) (map[string]interface{}, error) {
result, err := c.Call("get_distro_as_rendered", name, c.Token)
if err != nil {
return nil, err
}
return result.(map[string]interface{}), nil
}
20 changes: 20 additions & 0 deletions distro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,26 @@ func TestGetDistroHandle(t *testing.T) {
}
}

func TestGetValidDistroBootLoaders(t *testing.T) {
c := createStubHTTPClientSingle(t, "get-valid-distro-boot-loaders")
res, err := c.GetValidDistroBootLoaders("Ubuntu-20.04-x86_64")
FailOnError(t, err)

if len(res) < 1 {
t.Error("Expected at least one boot loader.")
}
}

func TestGetDistroAsRendered(t *testing.T) {
c := createStubHTTPClientSingle(t, "get-distro-as-rendered")
res, err := c.GetDistroAsRendered("Ubuntu-20.04-x86_64")
FailOnError(t, err)

if res["name"] != "Ubuntu-20.04-x86_64" {
t.Errorf("Wrong distro name returned: %v", res["name"])
}
}

/*
* NOTE: We're skipping the testing of CREATE, UPDATE, DELETE methods for now because
* the current implementation of the StubHTTPClient does not allow
Expand Down
16 changes: 16 additions & 0 deletions fixtures/clear-system-logs-req.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>clear_system_logs</methodName>
<params>
<param>
<value>
<string>system::testsys</string>
</value>
</param>
<param>
<value>
<string>securetoken99</string>
</value>
</param>
</params>
</methodCall>
8 changes: 8 additions & 0 deletions fixtures/clear-system-logs-res.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><boolean>1</boolean></value>
</param>
</params>
</methodResponse>
16 changes: 16 additions & 0 deletions fixtures/disable-netboot-req.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>disable_netboot</methodName>
<params>
<param>
<value>
<string>testsys</string>
</value>
</param>
<param>
<value>
<string>securetoken99</string>
</value>
</param>
</params>
</methodCall>
8 changes: 8 additions & 0 deletions fixtures/disable-netboot-res.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value><boolean>1</boolean></value>
</param>
</params>
</methodResponse>
16 changes: 16 additions & 0 deletions fixtures/get-distro-as-rendered-req.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>get_distro_as_rendered</methodName>
<params>
<param>
<value>
<string>Ubuntu-20.04-x86_64</string>
</value>
</param>
<param>
<value>
<string>securetoken99</string>
</value>
</param>
</params>
</methodCall>
17 changes: 17 additions & 0 deletions fixtures/get-distro-as-rendered-res.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value>
<struct>
<member>
<name>name</name>
<value>
<string>Ubuntu-20.04-x86_64</string>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodResponse>
16 changes: 16 additions & 0 deletions fixtures/get-menu-as-rendered-req.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>get_menu_as_rendered</methodName>
<params>
<param>
<value>
<string>testmenu</string>
</value>
</param>
<param>
<value>
<string>securetoken99</string>
</value>
</param>
</params>
</methodCall>
23 changes: 23 additions & 0 deletions fixtures/get-menu-as-rendered-res.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version='1.0'?>
<methodResponse>
<params>
<param>
<value>
<struct>
<member>
<name>name</name>
<value>
<string>testmenu</string>
</value>
</member>
<member>
<name>display_name</name>
<value>
<string></string>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodResponse>
Loading
Loading