Skip to content

Restructure Packages #352

Description

@thde

Restructure Packages (revised)

Revision of the original proposal after api/util and api/validation were dissolved in ca68f52, c4138d8, f942c04 and 658676f. Those commits moved shared logic into resource packages (internal/application, internal/bucket, api/gitinfo), not into a flat internal/api. This revision keeps that direction and drops the parts of the original that would work against it.

Goal

Make the dependency graph match the layering we already talk about:

main.go
  └── verb packages (get, create, ...)     one per CLI verb, never import each other
        └── resource packages (internal/application, internal/bucket, ...)
              └── api, api/config, api/log, api/gitinfo
                    └── internal/cli      (Error, exit codes, command hints)

Presentation packages (internal/format, internal/logbox) sit beside the verbs and are imported downward only, never by api.

1. Verb packages must not import each other (highest value)

Today create imports apply, auth and logs; update imports create; exec imports get. Anything two verbs share is by definition not verb-specific and belongs in a resource package, following what ca68f52 did for applications.

Concrete moves:

  • create.{ApplicationKongVars, BucketKongVars, KubernetesClusterOptions, SSHKeysFlags, LabelSelector, LocalReference, Target, StorageKeysWithDeprecatedFile} (used by update) → shared flag/option types go to internal/application, internal/bucket, and a new internal/kubernetescluster.
  • get.{ConnectionSecretMap, WriteBase64} (used by exec) → internal/secret or the owning resource package.
  • logs.{ApplicationQuery, BuildQuery, BuildsOfAppQuery} (used by create) → api/log, which already owns log queries.
  • apply.File (used by create) → api (it is a generic "apply this manifest" client operation).
  • auth.{Enabled, ApplyToSecret, Valid, ClusterCmd} (used by create) → the token/secret pieces to api/config or a new internal/auth; ClusterCmd needs a look because create/vcluster.go currently constructs another verb's command struct directly.

Add a guard so this does not regress: a small test in main_test.go (or a depguard rule in .golangci.yml) that lists the verb packages and fails if any of them imports another one.

2. Do not merge cli, format and logbox; fix their responsibilities instead

The original proposal merged them into internal/render. That would be a step backwards:

  • internal/cli is not a presentation package. It holds Error with exit codes and suggestions, and it is imported by api and api/config. Merging it into a package that pulls in bubbletea, lipgloss, loki logcli, yacspin and kong would make the API layer depend on all of that.
  • internal/format already mixes four concerns: object printing, progress/spinner output, kong plumbing, and command-hint strings. A merge would add error types and a TUI on top. That is exactly the grab-bag the Go guidance warns about, just with a nicer name.
  • internal/logbox is used by create only and carries the heaviest third-party deps. Keeping it separate is the point.

Instead:

  • internal/cli becomes the small "CLI contract" package: Error, exit codes, and the command-hint builder currently at format.Command(), since its only purpose is feeding Error.WithSuggestions. After that, api and api/config import cli only, and cli no longer imports format (its single use, format.Failuref, moves or gets inlined).
  • ManagedByAnnotation, Name and IsManagedBy move out of cli into api. They describe Kubernetes object annotations, not CLI behaviour, and api.DefaultAnnotations is their main consumer.
  • InterpolateFlagPlaceholders and MissingChildren are kong plumbing used by main.go only. Move them to the main package, or to internal/kongutil if a second user appears.
  • internal/format keeps printing, writer/reader, progress and interactivity detection. internal/logbox stays as is.

3. Rename internal/test to internal/testutil

Keep. It is a fixture and mock package, and testutil is the common name for that. Only _test.go files import it, so this is a pure gopls rename. Longer term, resource-specific fixtures (apps.go, bucket.go, mysql.go, ...) could sit next to their resource packages as internal/application/applicationtest and so on, but that is optional and should wait until step 1 settles where those packages are.

4. Move verb packages under internal/cmd/ (last, mechanical)

No repository outside ninech/nctl imports these packages as far as GitHub code search can tell, so internal/ adds intent, not protection. The move touches every command file and every open PR. Do it as one mechanical commit after steps 1 to 3, with gopls rename or gofmt -r, and coordinate with #372 and #225 which touch create and internal/application heavily. Package names stay get, create, ... so main.go wiring does not change beyond import paths.

Target layout:

nctl/
├── main.go
├── api/                  client, list, annotations, DefaultAnnotations
│   ├── config/
│   ├── gitinfo/
│   └── log/              incl. Application/Build queries
├── internal/
│   ├── cli/              Error, exit codes, command hints
│   ├── format/           printing, writer/reader, progress
│   ├── logbox/           build log TUI (create only)
│   ├── application/      shared app logic and flag types
│   ├── bucket/
│   ├── kubernetescluster/
│   ├── secret/
│   ├── apifield/, apiresource/, completion/, ipcheck/
│   ├── testutil/
│   └── cmd/
│       ├── get/ create/ apply/ update/ delete/ edit/
│       └── auth/ logs/ exec/ copy/
└── completions/

Order and PR split

  1. Break verb-to-verb imports, one PR per edge (update→create is the largest). Add the import guard in the first of these.
  2. Split cli/format responsibilities as described in 2.
  3. Rename internal/test to internal/testutil.
  4. Move verbs to internal/cmd/ in a single commit.

Each step compiles and passes tests on its own. Step 4 can be dropped entirely without losing the benefits of 1 to 3.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions