Skip to content

Commit

Permalink
projects: Add cloudapi support to depsolve command
Browse files Browse the repository at this point in the history
If the cloudapi is available it will use it to depsolve the list of
packages/projects on the cmdline. It defaults to using the host's
distribution and architecture but these can be overridden with the
`--distro` and `--arch` cmdline arguments.

Resolves: RHEL-60139
  • Loading branch information
bcl committed Mar 10, 2025
1 parent e24ddc4 commit 3eafd6e
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 24 deletions.
92 changes: 68 additions & 24 deletions cmd/composer-cli/projects/depsolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,28 @@ import (
"github.com/spf13/cobra"

"github.com/osbuild/weldr-client/v2/cmd/composer-cli/root"
"github.com/osbuild/weldr-client/v2/internal/common"
)

var (
depsolveCmd = &cobra.Command{
Use: "depsolve PROJECT,...",
Short: "Show the dependencies of all of the listed projects",
Long: ` By default this uses the host's distribution type and architecture when
depsolving. These can be overridden using the --distro and --arch flags. Use
'composer-cli distros list' to show the list of supported distributions.`,
Example: ` composer-cli projects depsolve tmux
composer-cli projects depsolve tmux --json
composer-cli projects depsolve tmux --distro fedora-38`,
composer-cli projects depsolve tmux --distro fedora-38
composer-cli projects depsolve tmux --distro fedora-38 --arch aarch64`,
RunE: depsolve,
Args: cobra.MinimumNArgs(1),
}
)

func init() {
depsolveCmd.Flags().StringVarP(&distro, "distro", "", "", "Return results for distribution")
depsolveCmd.Flags().StringVarP(&distro, "distro", "", "", "Distribution")
depsolveCmd.Flags().StringVarP(&arch, "arch", "", "", "Architecture")
projectsCmd.AddCommand(depsolveCmd)
}

Expand All @@ -50,31 +56,69 @@ func (p project) String() string {
func depsolve(cmd *cobra.Command, args []string) (rcErr error) {
names := root.GetCommaArgs(args)

deps, errors, err := root.Client.DepsolveProjects(names, distro)
if err != nil {
return root.ExecutionError(cmd, "Depsolve Error: %s", err)
}
if len(errors) > 0 {
rcErr = root.ExecutionErrors(cmd, errors)
}
var err error
if root.Cloud.Exists() {
if len(distro) == 0 {
distro, err = common.GetHostDistroName()
if err != nil {
return root.ExecutionError(cmd, "Error determining host distribution: %s", err)
}
}

// Encode it using json
data := new(bytes.Buffer)
if err := json.NewEncoder(data).Encode(deps); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: converting deps: %s\n", err)
return root.ExecutionError(cmd, "")
}
if len(arch) == 0 {
arch = common.HostArch()
}

// Decode the dependencies
var projects []project
if err = json.Unmarshal(data.Bytes(), &projects); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: decoding deps: %s\n", err)
return root.ExecutionError(cmd, "")
}
type pkg struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
}
blueprint := struct {
Name string `json:"name"`
Version string `json:"version"`
Packages []pkg `json:"packages"`
}{
Name: "projects-depsolve",
Version: "0.0.0",
Packages: []pkg{},
}
for _, name := range names {
blueprint.Packages = append(blueprint.Packages, pkg{Name: name})
}

for _, p := range projects {
fmt.Printf(" %s\n", p)
}
deps, err := root.Cloud.DepsolveBlueprint(blueprint, distro, arch)
if err != nil {
return root.ExecutionError(cmd, "Depsolve Error: %s", err)
}
for _, d := range deps {
fmt.Printf(" %s\n", d)
}
} else {
deps, errors, err := root.Client.DepsolveProjects(names, distro)
if err != nil {
return root.ExecutionError(cmd, "Depsolve Error: %s", err)
}
if len(errors) > 0 {
rcErr = root.ExecutionErrors(cmd, errors)
}

// Encode it using json
data := new(bytes.Buffer)
if err := json.NewEncoder(data).Encode(deps); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: converting deps: %s\n", err)
return root.ExecutionError(cmd, "")
}

// Decode the dependencies
var projects []project
if err = json.Unmarshal(data.Bytes(), &projects); err != nil {
fmt.Fprintf(os.Stderr, "ERROR: decoding deps: %s\n", err)
return root.ExecutionError(cmd, "")
}

for _, p := range projects {
fmt.Printf(" %s\n", p)
}
}
return rcErr
}
48 changes: 48 additions & 0 deletions cmd/composer-cli/projects/depsolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -379,3 +379,51 @@ func TestCmdProjectsDepsolveUnknownJSON(t *testing.T) {
assert.Equal(t, []byte(""), stderr)
assert.Equal(t, "GET", mc.Req.Method)
}

func TestCmdProjectsDepsolveCloud(t *testing.T) {
// Test the "blueprint depsolve" command with a local blueprint file
mcc := root.SetupCloudCmdTest(func(request *http.Request) (*http.Response, error) {
json := `{
"packages": [
{
"arch": "noarch",
"checksum": "sha256:930722d893b77edf204d16d9f9c6403ecefe339036b699bc445ad9ab87e0e323",
"name": "basesystem",
"release": "21.fc41",
"type": "rpm",
"version": "11"
},
{
"arch": "x86_64",
"checksum": "sha256:b10f7b9039bd3079d27e9883cd412f66acdac73b530b336c8c33e105a26391e8",
"name": "bash",
"release": "1.fc41",
"type": "rpm",
"version": "5.2.32"
}]
}`

return &http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewReader([]byte(json))),
}, nil
})

cmd, out, err := root.ExecuteTest("projects", "depsolve", "bash")
require.NotNil(t, out)
defer out.Close()
require.Nil(t, err)
require.NotNil(t, out.Stdout)
require.NotNil(t, out.Stderr)
require.NotNil(t, cmd)
assert.Equal(t, cmd, depsolveCmd)
stdout, err := io.ReadAll(out.Stdout)
assert.Nil(t, err)
assert.NotContains(t, string(stdout), "{")
assert.Contains(t, string(stdout), "basesystem-11-21.fc41.noarch")
assert.Contains(t, string(stdout), "bash-5.2.32-1.fc41.x86_64")
stderr, err := io.ReadAll(out.Stderr)
assert.Nil(t, err)
assert.Equal(t, []byte(""), stderr)
assert.Equal(t, "POST", mcc.Req.Method)
}

0 comments on commit 3eafd6e

Please sign in to comment.