Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
strategy:
matrix:
cobbler_version:
- v4.0.0b6
- v4.0.0b7
fail-fast: false
steps:
- name: Check out code into the Go module directory
Expand Down Expand Up @@ -59,7 +59,7 @@ jobs:
*.iso
key: ${{ steps.cache-iso-restore.outputs.cache-primary-key }}
- name: Upload coverage report to GH artifacts
if: matrix.cobbler_version == 'v4.0.0b5'
if: matrix.cobbler_version == 'v4.0.0b7'
# https://github.com/actions/upload-artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Clean break — talks only to Cobbler 4.0.0+. For 3.3.x servers, stay on the
- `--page` and `--items-per-page` flags on every `find` subcommand
(routes through `find_items_paged`).
- `cobbler setting export`.
- `cobbler check` — reports potential Cobbler server configuration problems.

### Removed (no longer accepted)

Expand Down
46 changes: 46 additions & 0 deletions cmd/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright SUSE LLC

package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

// NewCheckCmd builds a new command that represents the check action
func NewCheckCmd() *cobra.Command {
checkCmd := &cobra.Command{
Use: "check",
Short: "Check the Cobbler server configuration for common problems.",
Long: `This command asks the Cobbler server to inspect its own configuration (settings, installed
dependencies, boot-loader files, services, etc.) and reports suggestions for items you may want to fix. It
does not change anything on the server; it only lists potential problems for the administrator to review.`,
RunE: func(cmd *cobra.Command, args []string) error {
err := generateCobblerClient()
if err != nil {
return err
}

checks, err := Client.Check()
if err != nil {
return err
}

if len(*checks) == 0 {
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "No configuration problems found.")
return nil
}

_, _ = fmt.Fprintln(cmd.OutOrStdout(), "The following are potential configuration items that you may want to fix:")
for i, check := range *checks {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%d : %s\n", i+1, check)
}
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "Restart cobblerd and then run 'cobbler sync' to apply changes.")

return nil
},
}
return checkCmd
}
37 changes: 37 additions & 0 deletions cmd/check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cmd

import (
"bytes"
"github.com/cobbler/cobblerclient"
"github.com/spf13/cobra"
"io"
"strings"
"testing"
)

func Test_CheckCmd(t *testing.T) {
// Arrange
cobra.OnInitialize(initConfig, setupLogger)
rootCmd := NewRootCmd()
rootCmd.SetArgs([]string{"--config", "../testing/.cobbler.yaml", "check"})
stdout := bytes.NewBufferString("")
stderr := bytes.NewBufferString("")
rootCmd.SetOut(stdout)
rootCmd.SetErr(stderr)

// Act
err := rootCmd.Execute()

// Assert
cobblerclient.FailOnError(t, err)
FailOnNonEmptyStream(t, stderr)
stdoutBytes, err := io.ReadAll(stdout)
if err != nil {
t.Fatal(err)
}
stdoutString := string(stdoutBytes)
if !strings.Contains(stdoutString, "No configuration problems found.") &&
!strings.Contains(stdoutString, "The following are potential configuration items") {
t.Fatalf("expected one of the known check-command headers in output, got: %q", stdoutString)
}
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ func NewRootCmd() *cobra.Command {
// Add sub commands
rootCmd.AddCommand(NewAclSetupCmd())
rootCmd.AddCommand(NewBuildisoCmd())
rootCmd.AddCommand(NewCheckCmd())
distroCmd, err := NewDistroCmd()
cobra.CheckErr(err)
rootCmd.AddCommand(distroCmd)
Expand Down
13 changes: 13 additions & 0 deletions cmd/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,22 @@ import (
"time"
)

// convertMapStringToMapInterface converts the flat map[string]string produced by a stringToString flag (e.g.
// --autoinstall-meta) into the map[string]interface{} expected by the XML-RPC API. As a fallback for values that
// need to carry a list or dict rather than a flat string (e.g. Agama's agama_modules or cloud-init's per-module
// settings), any value that looks like a JSON array or object (starts with '[' or '{') is decoded as JSON; if it
// fails to parse, the original string is used unchanged so existing flat-string usage is unaffected.
func convertMapStringToMapInterface(m map[string]string) map[string]interface{} {
result := make(map[string]interface{})
for k, v := range m {
trimmed := strings.TrimSpace(v)
if len(trimmed) > 0 && (trimmed[0] == '[' || trimmed[0] == '{') {
var decoded interface{}
if err := json.Unmarshal([]byte(trimmed), &decoded); err == nil {
result[k] = decoded
continue
}
}
result[k] = v
}
return result
Expand Down
44 changes: 44 additions & 0 deletions cmd/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,50 @@ import (
"time"
)

func Test_convertMapStringToMapInterface(t *testing.T) {
tests := []struct {
name string
args map[string]string
want map[string]interface{}
}{
{
name: "flat string value is left unchanged",
args: map[string]string{"foo": "bar"},
want: map[string]interface{}{"foo": "bar"},
},
{
name: "json list value is decoded",
args: map[string]string{"agama_modules": `["root", "user"]`},
want: map[string]interface{}{"agama_modules": []interface{}{"root", "user"}},
},
{
name: "json object value is decoded",
args: map[string]string{"cloud_init_set_hostname": `{"hostname": "example-host"}`},
want: map[string]interface{}{
"cloud_init_set_hostname": map[string]interface{}{"hostname": "example-host"},
},
},
{
name: "leading whitespace is tolerated",
args: map[string]string{"agama_modules": ` ["root"]`},
want: map[string]interface{}{"agama_modules": []interface{}{"root"}},
},
{
name: "malformed json-looking value falls back to the raw string",
args: map[string]string{"agama_modules": `[root`},
want: map[string]interface{}{"agama_modules": `[root`},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := convertMapStringToMapInterface(tt.args)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("convertMapStringToMapInterface() got = %v, want %v", got, tt.want)
}
})
}
}

func Test_covertFloatToTime(t *testing.T) {
type args struct {
t float64
Expand Down
1 change: 1 addition & 0 deletions docs/commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Long Usage:

cobbler aclsetup <commands/aclsetup>
cobbler buildiso <commands/buildiso>
cobbler check <commands/check>
cobbler distro <commands/distro>
cobbler hardlink <commands/hardlink>
cobbler image <commands/image>
Expand Down
15 changes: 15 additions & 0 deletions docs/commands/check.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
*************
cobbler check
*************

Check the Cobbler server configuration for common problems.

Example:

.. code-block:: shell

$ cobbler check

This command asks the Cobbler server to inspect its own configuration (settings, installed dependencies,
boot-loader files, services, etc.) and reports suggestions for items you may want to fix. It does not change
anything on the server; it only lists potential problems for the administrator to review.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module github.com/cobbler/cli
go 1.24

require (
github.com/cobbler/cobblerclient v1.0.0-rc6
github.com/cobbler/cobblerclient v1.0.0-rc7
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/cobbler/cobblerclient v1.0.0-rc6 h1:rLwH0DUB3H0PKcE+wxLyiWz1DWmH7UMMd4LSBqFwhjA=
github.com/cobbler/cobblerclient v1.0.0-rc6/go.mod h1:BoS4GMBvwnypRHcOMedyipm+0Pu7IP5pSX6A/DqU6mo=
github.com/cobbler/cobblerclient v1.0.0-rc7 h1:Qey8hX5GeLNIWWBACPSpE2ZPNYZ5putpzbO6weMQx0M=
github.com/cobbler/cobblerclient v1.0.0-rc7/go.mod h1:BoS4GMBvwnypRHcOMedyipm+0Pu7IP5pSX6A/DqU6mo=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
6 changes: 4 additions & 2 deletions testing/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ services:
image: ghcr.io/cobbler/cobblerd:${COBBLER_IMAGE_TAG:-latest}
container_name: cobbler-dev
environment:
- COBBLER_XMLRPC_BIND_ADDRESS=0.0.0.0
- COBBLER_XMLRPC_BIND_ADDRESS_V4=0.0.0.0
volumes:
- ../extracted_iso_image:/extracted_iso_image:ro,z
ports:
- 25151:25151
command: cobblerd -F
# The published cobblerd image is missing `wget`, which the server's `check` action shells
# out to probe; without it, `cobbler check` faults instead of reporting it as a suggestion.
command: sh -c "zypper --non-interactive install wget && cobblerd -F"
2 changes: 1 addition & 1 deletion testing/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ if [ -z "$1" ]
echo "No cobbler server url supplied"
fi

cobbler_image_tag=v4.0.0b6
cobbler_image_tag=v4.0.0b7
iso_url=https://cdimage.ubuntu.com/ubuntu-legacy-server/releases/20.04/release/ubuntu-20.04.1-legacy-server-amd64.iso
iso_os=ubuntu
valid_iso_checksum=00a9d46306fbe9beb3581853a289490bc231c51f
Expand Down
Loading