Skip to content

Commit fb17d06

Browse files
committed
feat(debug): add comprehensive debug logging across all layers
Cover API layer (compute, network, image, volume, LB, cloud), system infrastructure (SSH, self-update, config, quota), app core (connect, routing), and 22 previously uncovered UI views with shared.Debugf() calls using consistent start/success/error triplets and module tags.
1 parent e327ebe commit fb17d06

45 files changed

Lines changed: 604 additions & 3 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/internal/app/connect.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,14 @@ import (
1010
)
1111

1212
func (m Model) connectToCloud(name string) tea.Cmd {
13+
shared.Debugf("[app] connectToCloud: start cloud=%s", name)
1314
return func() tea.Msg {
1415
client, err := cloud.Connect(context.Background(), name)
1516
if err != nil {
17+
shared.Debugf("[app] connectToCloud: error: %v", err)
1618
return shared.CloudConnectErrMsg{Err: err}
1719
}
20+
shared.Debugf("[app] connectToCloud: success cloud=%s", name)
1821
return shared.CloudConnectedMsg{
1922
ComputeClient: client.Compute,
2023
ImageClient: client.Image,
@@ -30,6 +33,11 @@ func (m Model) connectToCloud(name string) tea.Cmd {
3033

3134
func (m Model) switchToCloudPicker() (Model, tea.Cmd) {
3235
clouds, err := cloud.ListCloudNames()
36+
if err != nil {
37+
shared.Debugf("[app] switchToCloudPicker: error listing clouds: %v", err)
38+
} else {
39+
shared.Debugf("[app] switchToCloudPicker: found %d clouds", len(clouds))
40+
}
3341
m.cloudPicker = cloudpicker.New(clouds, err)
3442
m.cloudPicker.SetSize(m.width, m.height)
3543
m.view = viewCloudPicker

src/internal/app/routing.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ func (m Model) updateModal(msg tea.Msg) (Model, tea.Cmd) {
169169
}
170170

171171
func (m Model) handleDetailNavigation(msg shared.NavigateToDetailMsg) (Model, tea.Cmd) {
172+
shared.Debugf("[app] handleDetailNavigation: resource=%s id=%s", msg.Resource, msg.ID)
172173
switch msg.Resource {
173174
case "volume":
174175
m.volumeDetail = volumedetail.New(m.client.BlockStorage, m.client.Compute, msg.ID)
@@ -191,6 +192,7 @@ func (m Model) handleDetailNavigation(msg shared.NavigateToDetailMsg) (Model, te
191192
}
192193

193194
func (m Model) handleResourceNavigation(msg shared.NavigateToResourceMsg) (Model, tea.Cmd) {
195+
shared.Debugf("[app] handleResourceNavigation: tab=%s", msg.Tab)
194196
// Find the target tab index
195197
tabIdx := -1
196198
for i, td := range m.tabs {
@@ -220,6 +222,7 @@ func (m Model) handleResourceNavigation(msg shared.NavigateToResourceMsg) (Model
220222
}
221223

222224
func (m Model) handleViewChange(msg shared.ViewChangeMsg) (Model, tea.Cmd) {
225+
shared.Debugf("[app] handleViewChange: target=%s", msg.View)
223226
switch msg.View {
224227
case "serverlist":
225228
// If returning from a cross-resource jump, go back to the originating view
@@ -342,6 +345,7 @@ func (m Model) handleViewChange(msg shared.ViewChangeMsg) (Model, tea.Cmd) {
342345
}
343346

344347
func (m Model) forceRefreshActiveView() (Model, tea.Cmd) {
348+
shared.Debugf("[app] forceRefreshActiveView: view=%d", m.view)
345349
switch m.view {
346350
case viewServerList:
347351
return m, m.serverList.ForceRefresh()

src/internal/cloud/client.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"crypto/tls"
66
"fmt"
77

8+
"github.com/larkly/lazystack/internal/shared"
9+
810
"github.com/gophercloud/gophercloud/v2"
911
"github.com/gophercloud/gophercloud/v2/openstack"
1012
"github.com/gophercloud/gophercloud/v2/openstack/config"
@@ -26,17 +28,21 @@ type Client struct {
2628

2729
// Connect authenticates to the given cloud and initializes service clients.
2830
func Connect(ctx context.Context, cloudName string) (*Client, error) {
31+
shared.Debugf("[cloud] Connect: starting, cloud=%s", cloudName)
2932
ao, eo, tlsConfig, err := clouds.Parse(clouds.WithCloudName(cloudName), clouds.WithLocations(CloudsYamlPaths()...))
3033
if err != nil {
34+
shared.Debugf("[cloud] Connect: error parsing cloud config: %v", err)
3135
return nil, fmt.Errorf("parsing cloud %q: %w", cloudName, err)
3236
}
3337
return connectWithOpts(ctx, ao, eo, tlsConfig, cloudName)
3438
}
3539

3640
// ConnectWithProject authenticates scoped to a specific project.
3741
func ConnectWithProject(ctx context.Context, cloudName, projectID string) (*Client, error) {
42+
shared.Debugf("[cloud] ConnectWithProject: starting, cloud=%s projectID=%s", cloudName, projectID)
3843
ao, eo, tlsConfig, err := clouds.Parse(clouds.WithCloudName(cloudName), clouds.WithLocations(CloudsYamlPaths()...))
3944
if err != nil {
45+
shared.Debugf("[cloud] ConnectWithProject: error parsing cloud config: %v", err)
4046
return nil, fmt.Errorf("parsing cloud %q: %w", cloudName, err)
4147
}
4248
ao.TenantID = projectID
@@ -45,39 +51,56 @@ func ConnectWithProject(ctx context.Context, cloudName, projectID string) (*Clie
4551
}
4652

4753
func connectWithOpts(ctx context.Context, ao gophercloud.AuthOptions, eo gophercloud.EndpointOpts, tlsConfig *tls.Config, cloudName string) (*Client, error) {
54+
shared.Debugf("[cloud] connectWithOpts: authenticating to %s", cloudName)
4855
providerClient, err := config.NewProviderClient(ctx, ao, config.WithTLSConfig(tlsConfig))
4956
if err != nil {
57+
shared.Debugf("[cloud] connectWithOpts: authentication error: %v", err)
5058
return nil, fmt.Errorf("authenticating to %q: %w", cloudName, err)
5159
}
5260

61+
shared.Debugf("[cloud] connectWithOpts: creating compute client")
5362
compute, err := openstack.NewComputeV2(providerClient, eo)
5463
if err != nil {
64+
shared.Debugf("[cloud] connectWithOpts: compute client error: %v", err)
5565
return nil, fmt.Errorf("compute client: %w", err)
5666
}
5767
compute.Microversion = "2.100"
5868

69+
shared.Debugf("[cloud] connectWithOpts: creating image client")
5970
image, err := openstack.NewImageV2(providerClient, eo)
6071
if err != nil {
72+
shared.Debugf("[cloud] connectWithOpts: image client error: %v", err)
6173
return nil, fmt.Errorf("image client: %w", err)
6274
}
6375

76+
shared.Debugf("[cloud] connectWithOpts: creating network client")
6477
network, err := openstack.NewNetworkV2(providerClient, eo)
6578
if err != nil {
79+
shared.Debugf("[cloud] connectWithOpts: network client error: %v", err)
6680
return nil, fmt.Errorf("network client: %w", err)
6781
}
6882

6983
// BlockStorage — try v3 first ("block-storage"), then v2, then v1 ("volume")
7084
// Different clouds register Cinder under different service types
85+
shared.Debugf("[cloud] connectWithOpts: creating block storage client")
7186
blockStorage := tryBlockStorage(providerClient, eo)
87+
if blockStorage == nil {
88+
shared.Debugf("[cloud] connectWithOpts: block storage client unavailable")
89+
}
7290

7391
// LoadBalancer (Octavia) — optional service
92+
shared.Debugf("[cloud] connectWithOpts: creating load balancer client")
7493
loadBalancer := tryLoadBalancer(providerClient, eo)
94+
if loadBalancer == nil {
95+
shared.Debugf("[cloud] connectWithOpts: load balancer client unavailable")
96+
}
7597

7698
region := eo.Region
7799
if region == "" {
78100
region = "default"
79101
}
80102

103+
shared.Debugf("[cloud] connectWithOpts: success, cloud=%s region=%s", cloudName, region)
81104
return &Client{
82105
CloudName: cloudName,
83106
Region: region,

src/internal/cloud/clouds.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"path/filepath"
77
"sort"
88

9+
"github.com/larkly/lazystack/internal/shared"
10+
911
"gopkg.in/yaml.v3"
1012
)
1113

@@ -16,6 +18,7 @@ type cloudsFile struct {
1618

1719
// ListCloudNames parses clouds.yaml and returns sorted cloud names.
1820
func ListCloudNames() ([]string, error) {
21+
shared.Debugf("[cloud] ListCloudNames: starting")
1922
paths := CloudsYamlPaths()
2023

2124
for _, p := range paths {
@@ -26,6 +29,7 @@ func ListCloudNames() ([]string, error) {
2629

2730
var cf cloudsFile
2831
if err := yaml.Unmarshal(data, &cf); err != nil {
32+
shared.Debugf("[cloud] ListCloudNames: error parsing %s: %v", p, err)
2933
return nil, fmt.Errorf("parsing %s: %w", p, err)
3034
}
3135

@@ -34,9 +38,11 @@ func ListCloudNames() ([]string, error) {
3438
names = append(names, name)
3539
}
3640
sort.Strings(names)
41+
shared.Debugf("[cloud] ListCloudNames: success, count=%d from=%s", len(names), p)
3742
return names, nil
3843
}
3944

45+
shared.Debugf("[cloud] ListCloudNames: no clouds.yaml found")
4046
return nil, fmt.Errorf("no clouds.yaml found (searched: %v)", paths)
4147
}
4248

src/internal/cloud/projects.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"context"
55
"fmt"
66

7+
"github.com/larkly/lazystack/internal/shared"
8+
79
"github.com/gophercloud/gophercloud/v2"
810
"github.com/gophercloud/gophercloud/v2/openstack"
911
"github.com/gophercloud/gophercloud/v2/openstack/identity/v3/projects"
@@ -18,8 +20,10 @@ type Project struct {
1820

1921
// ListAccessibleProjects returns projects the current user can access.
2022
func ListAccessibleProjects(ctx context.Context, providerClient *gophercloud.ProviderClient, eo gophercloud.EndpointOpts) ([]Project, error) {
23+
shared.Debugf("[cloud] ListAccessibleProjects: starting")
2124
identityClient, err := openstack.NewIdentityV3(providerClient, eo)
2225
if err != nil {
26+
shared.Debugf("[cloud] ListAccessibleProjects: identity client error: %v", err)
2327
return nil, fmt.Errorf("identity client: %w", err)
2428
}
2529

@@ -37,7 +41,9 @@ func ListAccessibleProjects(ctx context.Context, providerClient *gophercloud.Pro
3741
return true, nil
3842
})
3943
if err != nil {
44+
shared.Debugf("[cloud] ListAccessibleProjects: error: %v", err)
4045
return nil, fmt.Errorf("listing projects: %w", err)
4146
}
47+
shared.Debugf("[cloud] ListAccessibleProjects: success, count=%d", len(result))
4248
return result, nil
4349
}

src/internal/compute/actions.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/gophercloud/gophercloud/v2"
99
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/instanceactions"
1010
"github.com/gophercloud/gophercloud/v2/pagination"
11+
"github.com/larkly/lazystack/internal/shared"
1112
)
1213

1314
// Action is a simplified instance action.
@@ -21,6 +22,7 @@ type Action struct {
2122

2223
// ListActions fetches instance actions for a server.
2324
func ListActions(ctx context.Context, client *gophercloud.ServiceClient, serverID string) ([]Action, error) {
25+
shared.Debugf("[compute] listing actions for server %s", serverID)
2426
var result []Action
2527
err := instanceactions.List(client, serverID, nil).EachPage(ctx, func(_ context.Context, page pagination.Page) (bool, error) {
2628
extracted, err := instanceactions.ExtractInstanceActions(page)
@@ -39,7 +41,9 @@ func ListActions(ctx context.Context, client *gophercloud.ServiceClient, serverI
3941
return true, nil
4042
})
4143
if err != nil {
44+
shared.Debugf("[compute] list actions for server %s: %v", serverID, err)
4245
return nil, fmt.Errorf("listing actions for %s: %w", serverID, err)
4346
}
47+
shared.Debugf("[compute] listed %d actions for server %s", len(result), serverID)
4448
return result, nil
4549
}

src/internal/compute/flavors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/gophercloud/gophercloud/v2"
88
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/flavors"
99
"github.com/gophercloud/gophercloud/v2/pagination"
10+
"github.com/larkly/lazystack/internal/shared"
1011
)
1112

1213
// Flavor is a simplified representation of a Nova flavor.
@@ -20,6 +21,7 @@ type Flavor struct {
2021

2122
// ListFlavors fetches all available flavors.
2223
func ListFlavors(ctx context.Context, client *gophercloud.ServiceClient) ([]Flavor, error) {
24+
shared.Debugf("[compute] listing flavors")
2325
opts := flavors.ListOpts{}
2426

2527
var result []Flavor
@@ -40,7 +42,9 @@ func ListFlavors(ctx context.Context, client *gophercloud.ServiceClient) ([]Flav
4042
return true, nil
4143
})
4244
if err != nil {
45+
shared.Debugf("[compute] list flavors: %v", err)
4346
return nil, fmt.Errorf("listing flavors: %w", err)
4447
}
48+
shared.Debugf("[compute] listed %d flavors", len(result))
4549
return result, nil
4650
}

src/internal/compute/keypairs.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/gophercloud/gophercloud/v2"
1313
"github.com/gophercloud/gophercloud/v2/openstack/compute/v2/keypairs"
1414
"github.com/gophercloud/gophercloud/v2/pagination"
15+
"github.com/larkly/lazystack/internal/shared"
1516
"golang.org/x/crypto/ssh"
1617
)
1718

@@ -23,6 +24,7 @@ type KeyPair struct {
2324

2425
// ListKeyPairs fetches all keypairs.
2526
func ListKeyPairs(ctx context.Context, client *gophercloud.ServiceClient) ([]KeyPair, error) {
27+
shared.Debugf("[compute] listing keypairs")
2628
var result []KeyPair
2729
err := keypairs.List(client, keypairs.ListOpts{}).EachPage(ctx, func(_ context.Context, page pagination.Page) (bool, error) {
2830
extracted, err := keypairs.ExtractKeyPairs(page)
@@ -38,8 +40,10 @@ func ListKeyPairs(ctx context.Context, client *gophercloud.ServiceClient) ([]Key
3840
return true, nil
3941
})
4042
if err != nil {
43+
shared.Debugf("[compute] list keypairs: %v", err)
4144
return nil, fmt.Errorf("listing keypairs: %w", err)
4245
}
46+
shared.Debugf("[compute] listed %d keypairs", len(result))
4347
return result, nil
4448
}
4549

@@ -54,23 +58,27 @@ type KeyPairFull struct {
5458
// GenerateAndImportKeyPair generates a keypair locally and imports the public key.
5559
// algorithm is "rsa" or "ed25519". keySize is only used for RSA (e.g. 2048, 4096).
5660
func GenerateAndImportKeyPair(ctx context.Context, client *gophercloud.ServiceClient, name, algorithm string, keySize int) (*KeyPairFull, error) {
61+
shared.Debugf("[compute] generating and importing keypair %q (algorithm=%s)", name, algorithm)
5762
var pubKeyBytes []byte
5863
var privKeyPEM string
5964

6065
switch algorithm {
6166
case "ed25519":
6267
pub, priv, err := ed25519.GenerateKey(rand.Reader)
6368
if err != nil {
69+
shared.Debugf("[compute] generate ed25519 key %q: %v", name, err)
6470
return nil, fmt.Errorf("generating ed25519 key: %w", err)
6571
}
6672
sshPub, err := ssh.NewPublicKey(pub)
6773
if err != nil {
74+
shared.Debugf("[compute] convert ed25519 public key %q: %v", name, err)
6875
return nil, fmt.Errorf("converting ed25519 public key: %w", err)
6976
}
7077
pubKeyBytes = ssh.MarshalAuthorizedKey(sshPub)
7178

7279
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
7380
if err != nil {
81+
shared.Debugf("[compute] marshal ed25519 private key %q: %v", name, err)
7482
return nil, fmt.Errorf("marshaling ed25519 private key: %w", err)
7583
}
7684
privKeyPEM = string(pem.EncodeToMemory(&pem.Block{
@@ -84,10 +92,12 @@ func GenerateAndImportKeyPair(ctx context.Context, client *gophercloud.ServiceCl
8492
}
8593
privKey, err := rsa.GenerateKey(rand.Reader, keySize)
8694
if err != nil {
95+
shared.Debugf("[compute] generate rsa key %q (%d bits): %v", name, keySize, err)
8796
return nil, fmt.Errorf("generating rsa key (%d bits): %w", keySize, err)
8897
}
8998
sshPub, err := ssh.NewPublicKey(&privKey.PublicKey)
9099
if err != nil {
100+
shared.Debugf("[compute] convert rsa public key %q: %v", name, err)
91101
return nil, fmt.Errorf("converting rsa public key: %w", err)
92102
}
93103
pubKeyBytes = ssh.MarshalAuthorizedKey(sshPub)
@@ -103,22 +113,27 @@ func GenerateAndImportKeyPair(ctx context.Context, client *gophercloud.ServiceCl
103113
// Import via Nova
104114
kp, err := ImportKeyPair(ctx, client, name, pubKeyStr)
105115
if err != nil {
116+
shared.Debugf("[compute] generate and import keypair %q: %v", name, err)
106117
return nil, err
107118
}
119+
shared.Debugf("[compute] generated and imported keypair %q", name)
108120
kp.PrivateKey = privKeyPEM
109121
return kp, nil
110122
}
111123

112124
// ImportKeyPair imports an existing public key.
113125
func ImportKeyPair(ctx context.Context, client *gophercloud.ServiceClient, name, publicKey string) (*KeyPairFull, error) {
126+
shared.Debugf("[compute] importing keypair %q", name)
114127
opts := keypairs.CreateOpts{
115128
Name: name,
116129
PublicKey: publicKey,
117130
}
118131
kp, err := keypairs.Create(ctx, client, opts).Extract()
119132
if err != nil {
133+
shared.Debugf("[compute] import keypair %q: %v", name, err)
120134
return nil, fmt.Errorf("importing keypair %s: %w", name, err)
121135
}
136+
shared.Debugf("[compute] imported keypair %q", name)
122137
return &KeyPairFull{
123138
Name: kp.Name,
124139
Type: kp.Type,
@@ -128,10 +143,13 @@ func ImportKeyPair(ctx context.Context, client *gophercloud.ServiceClient, name,
128143

129144
// GetKeyPair fetches a single keypair by name.
130145
func GetKeyPair(ctx context.Context, client *gophercloud.ServiceClient, name string) (*KeyPairFull, error) {
146+
shared.Debugf("[compute] getting keypair %q", name)
131147
kp, err := keypairs.Get(ctx, client, name, keypairs.GetOpts{}).Extract()
132148
if err != nil {
149+
shared.Debugf("[compute] get keypair %q: %v", name, err)
133150
return nil, fmt.Errorf("getting keypair %s: %w", name, err)
134151
}
152+
shared.Debugf("[compute] got keypair %q", name)
135153
return &KeyPairFull{
136154
Name: kp.Name,
137155
Type: kp.Type,
@@ -141,9 +159,12 @@ func GetKeyPair(ctx context.Context, client *gophercloud.ServiceClient, name str
141159

142160
// DeleteKeyPair deletes a keypair by name.
143161
func DeleteKeyPair(ctx context.Context, client *gophercloud.ServiceClient, name string) error {
162+
shared.Debugf("[compute] deleting keypair %q", name)
144163
r := keypairs.Delete(ctx, client, name, keypairs.DeleteOpts{})
145164
if r.Err != nil {
165+
shared.Debugf("[compute] delete keypair %q: %v", name, r.Err)
146166
return fmt.Errorf("deleting keypair %s: %w", name, r.Err)
147167
}
168+
shared.Debugf("[compute] deleted keypair %q", name)
148169
return nil
149170
}

0 commit comments

Comments
 (0)