Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Pebble internals into an exportable package #226

Merged
merged 4 commits into from
May 29, 2023

Conversation

anpep
Copy link
Collaborator

@anpep anpep commented May 23, 2023

This PR guides Pebble towards an internal structure that enables us to create Pebble-based applications that implement custom commands and add functionality that is not relevant to the upstream Pebble repository. However, we don't want to create a full-blown fork of Pebble and have the maintenance burden of having to port changes from upstream to every fork; for this reason, this PR does two things:

  • Rename the internal package to internals so that it can be imported by other Pebble-based applications.
  • Refactor all CLI functionality from the main package to the internals/cli package, so that other Pebble-based applications can import this package without having to reimplement this functionality.

This PR does not intend to be exhaustive and instead aims to serve as the groundwork for achieving the described goal. Thanks for your reviews!

anpep added 3 commits May 23, 2023 09:57
The `internal` package name is special in Go: it makes the package
non-importable.  We rename it to `internals` so that we can import it
from other software while conveying the idea of it being of internal
use.
The `main` package is special in Go: it is reserved for the package
containing a `main()` function that serves as the entry point for the
program and it can't be imported.

This commit refactors all command-line functionality that will be
shared by other software into the `internals/cli` package, which will be
imported and used by the `main()` function to invoke the Pebble
command-line interface.
@@ -0,0 +1,431 @@
// Copyright (c) 2014-2020 Canonical Ltd
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to other reviewers: This file is a verbatim copy from everything that did not stay in cmd/pebble/main.go above. The main difference is the deferred recovery() hook is now moved one level up into Run().

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the helpful observation.

internals/cli/cmd_help.go Outdated Show resolved Hide resolved
internals/cli/export_test.go Outdated Show resolved Hide resolved
@flotter
Copy link
Contributor

flotter commented May 23, 2023

LGTM. Two minor corrections I think.

@benhoyt
Copy link
Contributor

benhoyt commented May 24, 2023

Can you show how you would actually create a new binary that adds (or removes) commands? Is the intention to rewrite cli.Run? But even then, how would you add your new commands? addCommand isn't exported, so it would seem to me at the least we'd need to export that function.

It also seems to me different binaries might want to have different subsets of commands, that is, to remove existing Pebble commands. And currently all the addCommand calls are done inside init() functions. That seems non-ideal, as they're already all added before the package importer gets a chance to adjust them.

What about having an explicit list in main(), something like? (I'm not recommending this exact API.)

func main() {
    cli.AddCommand(cli.Add)
    cli.AddCommand(cli.Autostart)
    cli.AddCommand(cli.Changes)
    /* ... add other commands ... */
    cli.AddCommand(cli.CmdInfo{  // add our custom command defined here
		Name:      "custom",
		ShortHelp: "short help",
		LongHelp:  "long help",
		Builder:   func() flags.Commander { return &cmdCustom{} },
		OptDescs:  customDescs,
	}
    })
    cli.Run() // error handling omitted
}

We'd also need to rework how to generate the help categories a bit, to make that adjustable.

@benhoyt
Copy link
Contributor

benhoyt commented May 24, 2023

A couple of other things:

  1. Do we need to export all of the internal packages like you're doing, or can we just export the cli package? Do we need to access internal details of the other packages?
  2. I wonder if the name internals is a bit confusing, as it's too close to internal, which has special meaning (but of course internals doesn't). Perhaps "private"? (Or maybe naming has been discussed already.)

@anpep
Copy link
Collaborator Author

anpep commented May 24, 2023

Can you show how you would actually create a new binary that adds (or removes) commands? Is the intention to rewrite cli.Run? But even then, how would you add your new commands? addCommand isn't exported, so it would seem to me at the least we'd need to export that function.

I have a working implementation of this which basically exports addCommand and ways to append a help category to the manual (as Gustavo suggested), however, I'm yet to make a good proposal since I wanted to limit the scope of this PR to just exporting the internal package.

It also seems to me different binaries might want to have different subsets of commands, that is, to remove existing Pebble commands. And currently all the addCommand calls are done inside init() functions. That seems non-ideal, as they're already all added before the package importer gets a chance to adjust them.

I think you have a valid point here; however, I'm not sure removing "core" functionality from Pebble is a desired feature, actually, the aim is quite the opposite: to move functionality that's not common to all Pebble stakeholders out of the upstream Pebble repository and extend it in other applications (currently with Termus specifically in mind).

What about having an explicit list in main(), something like? (I'm not recommending this exact API.)

I will try to make a proposal tomorrow and gather feedback from Gustavo before going down this rabbit hole (: But this seems like a sensible option we can discuss and evolve over time.

A couple of other things:

  1. Do we need to export all of the internal packages like you're doing, or can we just export the cli package? Do we need to access internal details of the other packages?

I think this is the way to go, since we might need access to e.g. osutil from within commands that have very specific functionality. Or, thinking about Termus, we might want to have direct access to the overlord package so that we can instantiate an overlord, start the daemon, etc.

With this in mind, it makes sense for the cli package to be inside internals and not cmd/pebble, for instance.

  1. I wonder if the name internals is a bit confusing, as it's too close to internal, which has special meaning (but of course internals doesn't). Perhaps "private"? (Or maybe naming has been discussed already.)

Naming this package has proven to be very tricky, but after some discussion, Gustavo suggested internals which seems to work for us because: (1) makes it obvious that this package exposes a private API and thus prone to change, and (2) it's close enough to the original word internal that doesn't trick people into thinking this package has a different scope than the original.

As always, really appreciate your feedback!

@anpep anpep self-assigned this May 24, 2023
@benhoyt
Copy link
Contributor

benhoyt commented May 25, 2023

@anpep All fair enough, thanks! Naming is hard. :-)

@niemeyer
Copy link
Contributor

Thanks for the nice review, @benhoyt. As Ángel points out, the close proximity with "internal" is intentional here. Idelly we want people to feel exactly the same way about both, as for now there's a single exception that we'll be respecting.

Copy link
Contributor

@niemeyer niemeyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That looks good, Ángel, thank you. Just one trivial comment for consideration in the future.

This merge will likely break most open PRs, unfortunately, but there's no way around it.

"github.com/canonical/pebble/client"
)

var RunMain = run
var RunMain = Run
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this?

@niemeyer niemeyer merged commit 0c64c21 into canonical:master May 29, 2023
This was referenced May 31, 2023
@benhoyt benhoyt mentioned this pull request Sep 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants