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

Tooling for Mac Distribution #324

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open

Tooling for Mac Distribution #324

wants to merge 9 commits into from

Conversation

doggydogworld
Copy link
Contributor

No description provided.

@doggydogworld doggydogworld requested a review from a team as a code owner February 7, 2025 23:11

By default, notarization is disabled and will output dryrun logs. To enable it you must either set the following options:
```shell
mac-distribution --apple-username="" --apple-password="" --signing-identity="" --bundle-id="" ...
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we support flags at all? Doesn't this just make it more likely that secrets get recorded in shell history files?

Comment on lines +41 to +42
However this isn't desirable in CI environments where notarization must happen. Enabling dryrun will "silently" cause a failure.
For convenience the `--force-notarization` flag is provided to fail in the scenario where creds are missing.
Copy link
Contributor

Choose a reason for hiding this comment

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

Most CI environments set a CI environment variable - what do you think about automatically forcing notarization when $CI is set?

// Dry run if no credentials are provided
dryRun := g.AppleUsername == "" || g.ApplePassword == "" || g.SigningID == ""
if dryRun && g.ForceNotarization {
return nil, fmt.Errorf("notarization credentials not provided and force-notarization is enabled")
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return nil, fmt.Errorf("notarization credentials not provided and force-notarization is enabled")
return nil, errors.New("notarization credentials not provided and force-notarization is enabled")

Comment on lines +18 to +19
// Retry sets the max number of attempts for a succesful notarization
retry int
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Retry sets the max number of attempts for a succesful notarization
retry int
maxRetries int

A well-named variable often eliminates the need for extra comments.

zip zipper.DirZipper
}

// Creds is
Copy link
Contributor

Choose a reason for hiding this comment

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

Creds is ......

cmd.Stdout = &stdout

err := cmd.Run()
out := strings.TrimSpace(stdout.String())
Copy link
Contributor

Choose a reason for hiding this comment

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

You could save some allocations by keeping a []byte until the last possible moment. The bytes package and the strings package share most of these utilities in common.


// CopyFile copies a file from src to dst.
func CopyFile(src, dst string) error {
w, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0755) // create or overwrite
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we be preserving the permissions of the original file rather than using 0755?


r, err := os.OpenFile(src, os.O_RDONLY, 0)
if err != nil {
return trace.Wrap(err)
Copy link
Contributor

Choose a reason for hiding this comment

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

On error should we be deleting the dst file or leaving it behind?

"github.com/gravitational/trace"
)

type DirZipper interface {
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 need an interface here? Looks like there's only one implementation of it..

}

if !opts.IncludePrefix {
path, _ = strings.CutPrefix(path, root+string(os.PathSeparator))
Copy link
Contributor

Choose a reason for hiding this comment

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

Is TrimPrefix more appropriate here?


App Bundle (.app)
```shell
mac-distribution package-app tsh tsh.app/
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of having a separate tool for every artifact, what do you think about having a single build tool with subcommands that we can extend as we convert more stuff? E.g. something like:

$ tbuild build-mac ... --notarize

type DefaultZipper struct{}

// ZipDir will zip the directory into the specified output file
func (z *DefaultZipper) ZipDir(dir string, out io.Writer, opts DirZipperOpts) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: Not a huge deal but I'd consider making "opts" into functional options so you can keep parameter list clean when you need to not pass any options.

return trace.Wrap(tool.NotarizeBinaries(c.Files))
}

func (g *GlobalFlags) InitNotaryTool() (*notarize.Tool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to be public?


func (g *GlobalFlags) InitNotaryTool() (*notarize.Tool, error) {
// Dry run if no credentials are provided
dryRun := g.AppleUsername == "" || g.ApplePassword == "" || g.SigningID == ""
Copy link
Contributor

Choose a reason for hiding this comment

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

More of a nit, but I would probably prefer if the dry run mode was an explicit flag and creds required if it's not set. Less room for mistake IMO.

log.Warn("notarization dry run enabled", "reason", "notarization credentials missing")
}

// Initialize notary tool
Copy link
Contributor

Choose a reason for hiding this comment

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

It's pretty clear what it does lol

Suggested change
// Initialize notary tool


// The Apple Notary Service requires a zip file for notarization.
// The files will be staged in a temporary directory and zipped for submission.
notaryDir, err := os.MkdirTemp("", "notarize-binaries-*")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we include something in the dir name to differentiate between different builds? Like a version number? Just for potential debugging/troubleshooting.


// RunCommand logs the command that would have been run.
func (d *DryRunner) RunCommand(path string, args ...string) (string, error) {
d.log.Info("dry run", "path", path, "args", args)
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking at usage, this will potentially log all sensitive CLI arguments like Apple username and password, right? For example, when we call notarytool. We should not do that.

"--output-format", "json",
}

stdout, err := t.cmdRunner.RunCommand("xcrun", args...)
Copy link
Contributor

Choose a reason for hiding this comment

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

Like mentioned above, in dry run mode this looks like it will dump all arguments, including password, into the logs.

"--apple-password", t.Creds.ApplePassword,
"--output-format", "json",
}
stdout, err := t.cmdRunner.RunCommand("xcrun", args...)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above regarding arguments logging.


// Package creates an app bundle from the provided info.
func (a *AppBundlePackager) Package() error {
if err := a.Info.validate(); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we validating this inside "Package()" rather than inside the NewAppBundlePackager "constructor"?

@r0mant r0mant requested a review from camscale February 8, 2025 00:07
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.

3 participants