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
3 changes: 2 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
version: "2"
run:
go: '1.24'

linters:
enable:
Expand Down
10 changes: 7 additions & 3 deletions internal/services/artifactregistry/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package artifactregistry
import (
"context"
"fmt"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -1000,11 +1001,14 @@ func (s *Service) renderDetailView() string {
rows = append(rows, components.KeyValue{Key: "Labels", Value: formatLabels(s.selectedItem.Labels)})
}
if s.selectedItem.CleanupPolicyCount > 0 {
cleanup := fmt.Sprintf("%d policies", s.selectedItem.CleanupPolicyCount)
var builder strings.Builder
builder.Grow(32)
builder.WriteString(strconv.Itoa(int(s.selectedItem.CleanupPolicyCount)))
builder.WriteString(" policies")
if s.selectedItem.CleanupPolicyDryRun {
cleanup += " (dry run)"
builder.WriteString(" (dry run)")
}
rows = append(rows, components.KeyValue{Key: "Cleanup Policies", Value: cleanup})
rows = append(rows, components.KeyValue{Key: "Cleanup Policies", Value: builder.String()})
}
if !s.selectedItem.CreateTime.IsZero() {
rows = append(rows, components.KeyValue{Key: "Created", Value: s.selectedItem.CreateTime.Format("2006-01-02 15:04:05")})
Expand Down
38 changes: 38 additions & 0 deletions internal/services/artifactregistry/service_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package artifactregistry

import (
"fmt"
"strconv"
"strings"
"testing"
)

func BenchmarkCleanupPolicy_Concat(b *testing.B) {
count := int32(5)
dryRun := true
b.ResetTimer()
for i := 0; i < b.N; i++ {
cleanup := fmt.Sprintf("%d policies", count)
if dryRun {
cleanup += " (dry run)"
}
_ = cleanup
}
}

func BenchmarkCleanupPolicy_BuilderStrconv(b *testing.B) {
count := int32(5)
dryRun := true
b.ResetTimer()
for i := 0; i < b.N; i++ {
var builder strings.Builder
// Capacity hint: 1 (digit) + 9 (" policies") + 10 (" (dry run)") = 20
builder.Grow(32)
builder.WriteString(strconv.Itoa(int(count)))
builder.WriteString(" policies")
if dryRun {
builder.WriteString(" (dry run)")
}
_ = builder.String()
}
}
2 changes: 1 addition & 1 deletion internal/services/cloudbuild/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ func parseRegionalName(name string) (region, id string) {
}

func splitPath(s string) []string {
var parts []string
parts := make([]string, 0, strings.Count(s, "/")+1)
start := 0
for i := 0; i < len(s); i++ {
if s[i] == '/' {
Expand Down
7 changes: 3 additions & 4 deletions internal/services/cloudbuild/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -750,10 +750,9 @@ func (s *Service) renderDetailView() string {
s.selectedItem.ID,
)

rows := []components.KeyValue{
{Key: "ID", Value: s.selectedItem.ID},
{Key: "Status", Value: components.RenderStatus(s.selectedItem.Status)},
}
rows := make([]components.KeyValue, 0, 16)
rows = append(rows, components.KeyValue{Key: "ID", Value: s.selectedItem.ID})
rows = append(rows, components.KeyValue{Key: "Status", Value: components.RenderStatus(s.selectedItem.Status)})
if s.selectedItem.StatusDetail != "" {
rows = append(rows, components.KeyValue{Key: "Status Detail", Value: s.selectedItem.StatusDetail})
}
Expand Down
7 changes: 3 additions & 4 deletions internal/services/cloudfunctions/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@
// (trigger type/detail, runtime, memory/CPU, entry point, source location,
// env var count) that the Cloud Run "Functions" tab does not.
//
// Create/Update remain out of scope (see the app's TODO reference table for
// why); Delete and the `call` Lifecycle action (added via the v1 API purely
// to invoke an existing Gen1 function — see CallFunction's doc comment for
// why v1 rather than v2) are both implemented.
// Create/Update remain out of scope; Delete and the `call` Lifecycle action
// (added via the v1 API purely to invoke an existing Gen1 function — see
// CallFunction's doc comment for why v1 rather than v2) are both implemented.
package cloudfunctions

import (
Expand Down
8 changes: 3 additions & 5 deletions internal/services/cloudfunctions/cloudfunctions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,15 @@ import (

const CacheTTL = 60 * time.Second

// TODO(create): Cloud Functions deploy is intentionally NOT implemented in
// this package's Create pass. Unlike gce/disks/gke/cloudsql/cloudrun, a real
// Cloud Functions deploy is intentionally NOT implemented.
// Unlike gce/disks/gke/cloudsql/cloudrun, a real
// `functions deploy` (v2 Functions.Create / Patch) requires a source code
// bundle — a GCS object (zip) or a repo/git reference — that the build step
// actually compiles and runs; there is no "empty" or placeholder source that
// produces a working function. Wiring a Create form here without a real
// source upload flow would submit a request that is guaranteed to fail the
// build, i.e. a broken API call, which this pass explicitly avoids. Full
// source upload (zip + GCS staging bucket resolution) is out of scope for
// this minimal Create pass; revisit as a follow-up once that upload flow
// exists.
// source upload (zip + GCS staging bucket resolution) is out of scope.

// -----------------------------------------------------------------------------
// Models & Msgs
Expand Down
2 changes: 1 addition & 1 deletion internal/services/iam/iam.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ func (s *Service) Reset() {
s.selectedKey = nil
s.iamBindings = nil
s.keys = nil
s.err = nil // Fix: Clear previous errors on reset
s.err = nil
s.table.SetCursor(0)
}

Expand Down
4 changes: 2 additions & 2 deletions internal/services/jobs/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ type Service struct {
filter components.FilterModel
filterSession components.FilterSession[core.Job]

jobs []core.Job
jobs []core.Job
viewState ViewState
selected *core.Job
selected *core.Job
}

func NewService(cache *core.Cache) *Service {
Expand Down
11 changes: 11 additions & 0 deletions internal/services/loadbalancing/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package loadbalancing
import (
"context"
"fmt"
"slices"
"strconv"
"strings"

Expand Down Expand Up @@ -93,6 +94,7 @@ func (c *Client) ListHealthChecks(projectID string) ([]HealthCheck, error) {
req := c.service.HealthChecks.AggregatedList(projectID)
err := req.Pages(context.Background(), func(page *compute.HealthChecksAggregatedList) error {
for scope, scoped := range page.Items {
result = slices.Grow(result, len(scoped.HealthChecks))
for _, hc := range scoped.HealthChecks {
port, checkType := healthCheckPortAndType(hc)
result = append(result, HealthCheck{
Expand Down Expand Up @@ -196,6 +198,15 @@ func (c *Client) ListForwardingRules(projectID string) ([]ForwardingRule, error)
var result []ForwardingRule
req := c.service.ForwardingRules.AggregatedList(projectID)
err := req.Pages(context.Background(), func(page *compute.ForwardingRuleAggregatedList) error {
count := 0
for _, scoped := range page.Items {
count += len(scoped.ForwardingRules)
}

if result == nil {
result = make([]ForwardingRule, 0, count)
}

for scope, scoped := range page.Items {
for _, fr := range scoped.ForwardingRules {
result = append(result, ForwardingRule{
Expand Down
4 changes: 2 additions & 2 deletions internal/services/pubsub/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func (c *Client) GetTopicIAMPolicy(projectID, topicID string) ([]IAMBinding, err
if err != nil {
return nil, fmt.Errorf("get topic IAM policy: %w", err)
}
var out []IAMBinding
out := make([]IAMBinding, 0, len(policy.Bindings))
for _, b := range policy.Bindings {
out = append(out, IAMBinding{Role: b.Role, Members: b.Members})
}
Expand Down Expand Up @@ -403,7 +403,7 @@ func (c *Client) GetSubscriptionIAMPolicy(projectID, subID string) ([]IAMBinding
if err != nil {
return nil, fmt.Errorf("get subscription IAM policy: %w", err)
}
var out []IAMBinding
out := make([]IAMBinding, 0, len(policy.Bindings))
for _, b := range policy.Bindings {
out = append(out, IAMBinding{Role: b.Role, Members: b.Members})
}
Expand Down
120 changes: 120 additions & 0 deletions internal/services/pubsub/api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package pubsub

import (
"reflect"
"testing"

"google.golang.org/api/pubsub/v1"
)

func TestMergeIAMBinding(t *testing.T) {
tests := []struct {
name string
bindings []*pubsub.Binding
role string
member string
expected []*pubsub.Binding
}{
{
name: "empty bindings",
bindings: nil,
role: "roles/pubsub.publisher",
member: "user:test@example.com",
expected: []*pubsub.Binding{
{
Role: "roles/pubsub.publisher",
Members: []string{"user:test@example.com"},
},
},
},
{
name: "role exists, member exists",
bindings: []*pubsub.Binding{
{
Role: "roles/pubsub.publisher",
Members: []string{"user:test@example.com"},
},
},
role: "roles/pubsub.publisher",
member: "user:test@example.com",
expected: []*pubsub.Binding{
{
Role: "roles/pubsub.publisher",
Members: []string{"user:test@example.com"},
},
},
},
{
name: "role exists, member missing",
bindings: []*pubsub.Binding{
{
Role: "roles/pubsub.publisher",
Members: []string{"user:test@example.com"},
},
},
role: "roles/pubsub.publisher",
member: "user:other@example.com",
expected: []*pubsub.Binding{
{
Role: "roles/pubsub.publisher",
Members: []string{"user:test@example.com", "user:other@example.com"},
},
},
},
{
name: "role does not exist",
bindings: []*pubsub.Binding{
{
Role: "roles/pubsub.publisher",
Members: []string{"user:test@example.com"},
},
},
role: "roles/pubsub.subscriber",
member: "user:other@example.com",
expected: []*pubsub.Binding{
{
Role: "roles/pubsub.publisher",
Members: []string{"user:test@example.com"},
},
{
Role: "roles/pubsub.subscriber",
Members: []string{"user:other@example.com"},
},
},
},
{
name: "multiple roles, role exists, member missing",
bindings: []*pubsub.Binding{
{
Role: "roles/pubsub.admin",
Members: []string{"user:admin@example.com"},
},
{
Role: "roles/pubsub.publisher",
Members: []string{"user:test@example.com"},
},
},
role: "roles/pubsub.publisher",
member: "user:other@example.com",
expected: []*pubsub.Binding{
{
Role: "roles/pubsub.admin",
Members: []string{"user:admin@example.com"},
},
{
Role: "roles/pubsub.publisher",
Members: []string{"user:test@example.com", "user:other@example.com"},
},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := mergeIAMBinding(tt.bindings, tt.role, tt.member)
if !reflect.DeepEqual(result, tt.expected) {
t.Errorf("mergeIAMBinding() = %v, want %v", result, tt.expected)
}
})
}
}
Loading
Loading