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
66 changes: 41 additions & 25 deletions src/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Manages virtual machine-related operations in your Kubernetes cluster.
| Command | Description |
|--------------------|------------------------------------------------------------------------|
| ansible-inventory | Generate ansible inventory from virtual machines |
| collect-debug-info | Collect debug information for VM: configuration, events, and logs |
| console | Connect to a console of a virtual machine. |
| port-forward | Forward local ports to a virtual machine. |
| scp | SCP files from/to a virtual machine. |
Expand All @@ -24,80 +25,95 @@ Manages virtual machine-related operations in your Kubernetes cluster.

```shell
# Get inventory for default namespace in JSON format
d8 virtualization ansible-inventory
d8 virtualization ansible-inventory --list
d8 v ansible-inventory
d8 v ansible-inventory --list

# Get host variables
d8 virtualization ansible-inventory --host myvm.mynamespace
d8 v ansible-inventory --host myvm.mynamespace

# Specify namespace
d8 virtualization ansible-inventory -n mynamespace
d8 v ansible-inventory -n mynamespace

# Specify output format (json, ini, yaml)
d8 virtualization ansible-inventory -o json
d8 virtualization ansible-inventory -o yaml
d8 virtualization ansible-inventory -o ini
d8 v ansible-inventory -o json
d8 v ansible-inventory -o yaml
d8 v ansible-inventory -o ini
```

#### console

```shell
d8 virtualization console myvm
d8 virtualization console myvm.mynamespace
d8 v console myvm
d8 v console myvm.mynamespace
```

#### port-forward

```shell
d8 virtualization port-forward myvm tcp/8080:8080
d8 virtualization port-forward --stdio=true myvm.mynamespace 22
d8 v port-forward myvm tcp/8080:8080
d8 v port-forward --stdio=true myvm.mynamespace 22
```

#### scp

```shell
d8 virtualization scp myfile.bin user@myvm:myfile.bin
d8 virtualization scp user@myvm:myfile.bin ~/myfile.bin
d8 v scp myfile.bin user@myvm:myfile.bin
d8 v scp user@myvm:myfile.bin ~/myfile.bin
```

#### ssh

```shell
d8 virtualization --identity-file=/path/to/ssh_key ssh user@myvm.mynamespace
d8 virtualization ssh --local-ssh=true --namespace=mynamespace --username=user myvm
d8 v --identity-file=/path/to/ssh_key ssh user@myvm.mynamespace
d8 v ssh --local-ssh=true --namespace=mynamespace --username=user myvm
```

#### vnc

```shell
d8 virtualization vnc myvm.mynamespace
d8 virtualization vnc myvm -n mynamespace
d8 v vnc myvm.mynamespace
d8 v vnc myvm -n mynamespace
```

#### start

```shell
d8 virtualization start myvm.mynamespace --wait
d8 virtualization start myvm -n mynamespace
d8 v start myvm.mynamespace --wait
d8 v start myvm -n mynamespace
```

#### stop

```shell
d8 virtualization stop myvm.mynamespace --force
d8 virtualization stop myvm -n mynamespace
d8 v stop myvm.mynamespace --force
d8 v stop myvm -n mynamespace
```

#### restart

```shell
d8 virtualization restart myvm.mynamespace --timeout=1m
d8 virtualization restart myvm -n mynamespace
d8 v restart myvm.mynamespace --timeout=1m
d8 v restart myvm -n mynamespace
```

#### evict

```shell
d8 virtualization evict myvm.mynamespace
d8 virtualization evict myvm -n mynamespace
d8 v evict myvm.mynamespace
d8 v evict myvm -n mynamespace
```

#### collect-debug-info

```shell
# Collect debug info for VirtualMachine 'myvm'
d8 v collect-debug-info myvm
d8 v collect-debug-info myvm.mynamespace
d8 v collect-debug-info myvm -n mynamespace

# Include pod logs in output
d8 v collect-debug-info --with-logs myvm

# Enable debug output for permission errors
d8 v collect-debug-info --debug myvm
```
13 changes: 13 additions & 0 deletions src/cli/internal/clientconfig/clientconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"fmt"

"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"

"github.com/deckhouse/virtualization/api/client/kubeclient"
Expand Down Expand Up @@ -49,3 +50,15 @@ func ClientAndNamespaceFromContext(ctx context.Context) (client kubeclient.Clien
}
return client, namespace, overridden, nil
}

func GetRESTConfig(ctx context.Context) (*rest.Config, error) {
clientConfig, ok := ctx.Value(clientConfigKey).(clientcmd.ClientConfig)
if !ok {
return nil, fmt.Errorf("unable to get client config from context")
}
config, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
return config, nil
}
128 changes: 128 additions & 0 deletions src/cli/internal/cmd/collectdebuginfo/collectdebuginfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
Copyright 2025 Flant JSC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package collectdebuginfo

import (
"context"
"fmt"
"io"

"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/client-go/dynamic"

"github.com/deckhouse/virtualization/api/client/kubeclient"
"github.com/deckhouse/virtualization/src/cli/internal/clientconfig"
"github.com/deckhouse/virtualization/src/cli/internal/templates"
)

func NewCommand() *cobra.Command {
bundle := &DebugBundle{}
cmd := &cobra.Command{
Use: "collect-debug-info (VirtualMachine)",
Short: "Collect debug information for VM: configuration, events, and logs. Output is written to stdout.",
Example: usage(),
Args: templates.ExactArgs("collect-debug-info", 1),
RunE: bundle.Run,
}

cmd.Flags().BoolVar(&bundle.saveLogs, "with-logs", false, "Include pod logs in output")
cmd.Flags().BoolVar(&bundle.debug, "debug", false, "Enable debug output for permission errors")
cmd.SetUsageTemplate(templates.UsageTemplate())
return cmd
}

type DebugBundle struct {
saveLogs bool
debug bool
dynamicClient dynamic.Interface
stdout io.Writer
stderr io.Writer
resourceCount int
}

func usage() string {
return ` # Collect debug info for VirtualMachine 'myvm' (output to stdout):
{{ProgramName}} collect-debug-info myvm
{{ProgramName}} collect-debug-info myvm.mynamespace
{{ProgramName}} collect-debug-info myvm -n mynamespace

# Include pod logs:
{{ProgramName}} collect-debug-info --with-logs myvm

# Save compressed output to file:
{{ProgramName}} collect-debug-info --with-logs myvm | gzip > debug-info.yaml.gz`
}

func (b *DebugBundle) Run(cmd *cobra.Command, args []string) error {
client, defaultNamespace, _, err := clientconfig.ClientAndNamespaceFromContext(cmd.Context())
if err != nil {
return err
}

namespace, name, err := templates.ParseTarget(args[0])
if err != nil {
return err
}
if namespace == "" {
namespace = defaultNamespace
}

config, err := clientconfig.GetRESTConfig(cmd.Context())
if err != nil {
return fmt.Errorf("failed to get REST config: %w", err)
}
b.dynamicClient, err = dynamic.NewForConfig(config)
if err != nil {
return fmt.Errorf("failed to create dynamic client: %w", err)
}

b.stdout = cmd.OutOrStdout()
b.stderr = cmd.ErrOrStderr()

if err := b.collectResources(cmd.Context(), client, namespace, name); err != nil {
return err
}

return nil
}

func (b *DebugBundle) collectResources(ctx context.Context, client kubeclient.Client, namespace, vmName string) error {
if err := b.collectVMResources(ctx, client, namespace, vmName); err != nil {
return fmt.Errorf("failed to collect VM resources: %w", err)
}

if err := b.collectBlockDevices(ctx, client, namespace, vmName); err != nil {
return fmt.Errorf("failed to collect block devices: %w", err)
}

if err := b.collectPods(ctx, client, namespace, vmName); err != nil {
return fmt.Errorf("failed to collect pods: %w", err)
}

return nil
}

func (b *DebugBundle) handleError(resourceType, resourceName string, err error) bool {
if errors.IsForbidden(err) || errors.IsUnauthorized(err) {
if b.debug {
_, _ = fmt.Fprintf(b.stderr, "Warning: Skipping %s/%s: permission denied\n", resourceType, resourceName)
}
return true
}
return false
}
Loading
Loading