Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Download notarization log, parse issues, error if any issues #6

Merged
merged 5 commits into from
Nov 6, 2019
Merged
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
88 changes: 82 additions & 6 deletions cmd/gon/item.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package main

import (
"context"
"fmt"
"os"
"sync"

"github.com/fatih/color"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-multierror"

"github.com/mitchellh/gon/internal/config"
"github.com/mitchellh/gon/notarize"
Expand Down Expand Up @@ -64,7 +66,7 @@ func (i *item) notarize(ctx context.Context, opts *processOptions) error {
}

// Start notarization
_, err := notarize.Notarize(ctx, &notarize.Options{
info, err := notarize.Notarize(ctx, &notarize.Options{
File: i.Path,
BundleId: bundleId,
Username: opts.Config.AppleId.Username,
Expand All @@ -75,18 +77,92 @@ func (i *item) notarize(ctx context.Context, opts *processOptions) error {
UploadLock: opts.UploadLock,
})

// Save our state
i.State.Notarized = err == nil
// Save the error state. We don't save the notarization result yet
// because we don't know it for sure until we download the log file.
i.State.NotarizeError = err

// After we're done we want to output information for this
// file right away.
lock.Lock()
// If we had an error, we mention immediate we have an error.
if err != nil {
lock.Lock()
color.New(color.FgRed).Fprintf(os.Stdout, " %sError notarizing\n", opts.Prefix)
lock.Unlock()
}

// If we have a log file, download it. We do this whether we have an error
// or not because the log file can contain more details about the error.
if info != nil && info.LogFileURL != "" {
opts.Logger.Info(
"downloading log file for notarization",
"request_uuid", info.RequestUUID,
"url", info.LogFileURL,
)

log, logerr := notarize.DownloadLog(info.LogFileURL)
opts.Logger.Debug("log file downloaded", "log", log, "err", logerr)
if logerr != nil {
opts.Logger.Warn(
"error downloading log file, this isn't a fatal error",
"err", err,
)

// If we already failed notarization, just return that error
if err := i.State.NotarizeError; err != nil {
return err
}

// If it appears we succeeded notification, we make a new error.
// We can't say notarization is successful without downloading this
// file because warnings will cause notarization to not work
// when loaded.
lock.Lock()
color.New(color.FgRed).Fprintf(os.Stdout,
" %sError downloading log file to verify notarization.\n",
opts.Prefix,
)
lock.Unlock()

return fmt.Errorf(
"Error downloading log file to verify notarization success: %s\n\n"+
"You can download the log file manually at: %s",
logerr, info.LogFileURL,
)
}

// If we have any issues then it is a failed notarization. Notarization
// can "succeed" with warnings, but when you attempt to use/open a file
// Gatekeeper rejects it. So we currently reject any and all issues.
if len(log.Issues) > 0 {
var err error

lock.Lock()
color.New(color.FgRed).Fprintf(os.Stdout,
" %s%d issues during notarization:\n",
opts.Prefix, len(log.Issues))
for idx, issue := range log.Issues {
color.New(color.FgRed).Fprintf(os.Stdout,
" %sIssue #%d (%s) for path %q: %s\n",
opts.Prefix, idx+1, issue.Severity, issue.Path, issue.Message)

// Append the error so we can return it
err = multierror.Append(err, fmt.Errorf(
"%s for path %q: %s",
issue.Severity, issue.Path, issue.Message,
))
}
lock.Unlock()

return err
}
}

// If we aren't notarized, then return
if err := i.State.NotarizeError; err != nil {
return err
}

// Save our state
i.State.Notarized = true
lock.Lock()
color.New(color.FgGreen).Fprintf(os.Stdout, " %sFile notarized!\n", opts.Prefix)
lock.Unlock()

Expand Down
2 changes: 1 addition & 1 deletion cmd/gon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ func realMain() int {

// If totalErr is not nil then we had one or more errors.
if totalErr != nil {
fmt.Fprintf(os.Stdout, color.RedString("❗️ Error notarizing:\n\n%s\n", totalErr))
fmt.Fprintf(os.Stdout, color.RedString("\n❗️ Error notarizing:\n\n%s\n", totalErr))
return 1
}

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ go 1.13
require (
github.com/davecgh/go-spew v1.1.1
github.com/fatih/color v1.7.0
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-hclog v0.9.3-0.20191025211905-234833755cb2
github.com/hashicorp/go-multierror v1.0.0
github.com/hashicorp/go-retryablehttp v0.6.3
github.com/hashicorp/hcl/v2 v2.0.0
github.com/sebdah/goldie v1.0.0
github.com/stretchr/testify v1.3.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,17 @@ github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FK
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.9.3-0.20191025211905-234833755cb2 h1:STV8OvzphW1vlhPFxcG8d6OIilzBSKRAoWFJt+Onu10=
github.com/hashicorp/go-hclog v0.9.3-0.20191025211905-234833755cb2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-retryablehttp v0.6.3 h1:tuulM+WnToeqa05z83YLmKabZxrySOmJAd4mJ+s2Nfg=
github.com/hashicorp/go-retryablehttp v0.6.3/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl/v2 v2.0.0 h1:efQznTz+ydmQXq3BOnRa3AXzvCeTq1P4dKj/z5GLlY8=
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
Expand Down
79 changes: 79 additions & 0 deletions notarize/log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package notarize

import (
"encoding/json"
"fmt"
"io"

"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/go-retryablehttp"
)

// Log is the structure that is available when downloading the log file
// that the notarization service creates.
//
// This may not be complete with all fields. I only included fields that
// I saw and even then only the more useful ones.
type Log struct {
JobId string `json:"jobId"`
Status string `json:"status"`
StatusSummary string `json:"statusSummary"`
StatusCode int `json:"statusCode"`
ArchiveFilename string `json:"archiveFilename"`
UploadDate string `json:"uploadDate"`
SHA256 string `json:"sha256"`
Issues []LogIssue `json:"issues"`
TicketContents []LogTicketContent `json:"ticketContents"`
}

// LogIssue is a single issue that may have occurred during notarization.
type LogIssue struct {
Severity string `json:"severity"`
Path string `json:"path"`
Message string `json:"message"`
}

// LogTicketContent is an entry that was noted as being within the archive.
type LogTicketContent struct {
Path string `json:"path"`
DigestAlgorithm string `json:"digestAlgorithm"`
CDHash string `json:"cdhash"`
Arch string `json:"arch"`
}

// These are the log severities that may exist.
const (
LogSeverityError = "error"
LogSeverityWarning = "warning"
)

// ParseLog parses a log from the given reader, such as an HTTP response.
func ParseLog(r io.Reader) (*Log, error) {
// Protect against this since it is common with HTTP responses.
if r == nil {
return nil, fmt.Errorf("nil reader given to ParseLog")
}

var result Log
return &result, json.NewDecoder(r).Decode(&result)
}

// DownloadLog downloads a log file and parses it using a default HTTP client.
// If you want more fine-grained control over the download, download it
// using your own client and use ParseLog.
func DownloadLog(path string) (*Log, error) {
// Build our HTTP client
client := retryablehttp.NewClient()
client.HTTPClient = cleanhttp.DefaultClient()

// Get it!
resp, err := client.Get(path)
if err != nil {
return nil, err
}
if resp.Body != nil {
defer resp.Body.Close()
}

return ParseLog(resp.Body)
}