Skip to content
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
23 changes: 0 additions & 23 deletions pkg/pm/installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (

"go.wpm.so/cli/pkg/archive"
"go.wpm.so/cli/pkg/pm/registry"
"go.wpm.so/cli/pkg/pm/signatures"
"go.wpm.so/cli/pkg/pm/wpmjson/types"
"go.wpm.so/cli/pkg/pm/wpmjson/validator"
)
Expand All @@ -40,7 +39,6 @@ type Installer struct {

client registry.Client
extractSem chan struct{}
keysJson signatures.KeysJson
logger func(format string, args ...any)
}

Expand Down Expand Up @@ -106,12 +104,6 @@ func sweepStaleRunDirs(tmpDir string) {
}

func (i *Installer) InstallAll(ctx context.Context, plan []Action, progressFn func(Action)) error {
keys, err := i.client.GetKeysJson(ctx)
if err != nil {
return fmt.Errorf("failed to fetch public keys for signature verification: %w", err)
}
i.keysJson = keys

g, ctx := errgroup.WithContext(ctx)
g.SetLimit(i.concurrency)

Expand Down Expand Up @@ -151,21 +143,6 @@ func (i *Installer) install(ctx context.Context, action Action) error {
}

func (i *Installer) installOrUpdate(ctx context.Context, action Action, targetDir string) error {
sigs := action.Signatures
if len(sigs) == 0 {
return fmt.Errorf("no signatures found for package %s@%s", action.Name, action.Version)
}

err := signatures.Verify(
i.keysJson,
sigs[0].KeyID,
sigs[0].Sig,
fmt.Appendf(nil, "%s:%s:%s", action.Name, action.Version, action.Digest),
)
if err != nil {
return fmt.Errorf("signature verification failed for package %s@%s: %w", action.Name, action.Version, err)
}

path := tarballPath(action.Name, action.Version)
resp, err := i.client.DownloadTarball(ctx, path)
if err != nil {
Expand Down
45 changes: 20 additions & 25 deletions pkg/pm/installer/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"go.wpm.so/cli/pkg/pm/resolution"
"go.wpm.so/cli/pkg/pm/wpmjson"
"go.wpm.so/cli/pkg/pm/wpmjson/manifest"
"go.wpm.so/cli/pkg/pm/wpmjson/types"
"go.wpm.so/cli/pkg/pm/wpmlock"
)
Expand All @@ -21,12 +20,11 @@ const (

// Action represents a single operation to be performed on the filesystem
type Action struct {
Type ActionType
Name string
Version string
Signatures []manifest.Signature
Digest string // Sha256 digest
PkgType types.PackageType
Type ActionType
Name string
Version string
Digest string // Sha256 digest
PkgType types.PackageType
}

// CalculatePlan determines filesystem operations based on lockfile, resolved tree, and flags.
Expand Down Expand Up @@ -109,34 +107,31 @@ func resolveAction(name string, node resolution.Node, lock *wpmlock.Lockfile, ex
oldPkg, inLock := lock.Packages[name]
if !inLock {
return Action{
Type: ActionInstall,
Name: name,
Version: node.Version,
Signatures: node.Signatures,
Digest: node.Digest,
PkgType: node.Type,
Type: ActionInstall,
Name: name,
Version: node.Version,
Digest: node.Digest,
PkgType: node.Type,
}, true
}

if oldPkg.Version != node.Version || oldPkg.Digest != node.Digest {
return Action{
Type: ActionUpdate,
Name: name,
Version: node.Version,
Signatures: node.Signatures,
Digest: node.Digest,
PkgType: node.Type,
Type: ActionUpdate,
Name: name,
Version: node.Version,
Digest: node.Digest,
PkgType: node.Type,
}, true
}

if !exists {
return Action{
Type: ActionInstall,
Name: name,
Version: node.Version,
Signatures: node.Signatures,
Digest: node.Digest,
PkgType: node.Type,
Type: ActionInstall,
Name: name,
Version: node.Version,
Digest: node.Digest,
PkgType: node.Type,
}, true
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/pm/registry/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type client struct {
// registry
type Client interface {
Whoami(ctx context.Context, token string) (string, error)
GetKeysJson(ctx context.Context) (signatures.KeysJson, error)
GetKeysJson(ctx context.Context) (signatures.Keys, error)
DownloadTarball(ctx context.Context, url string) (io.ReadCloser, error)
PutPackage(ctx context.Context, data *manifest.Package, tarball io.Reader) error
GetPackageManifest(ctx context.Context, packageName, versionOrTag string, force bool) (*manifest.Package, error)
Expand Down Expand Up @@ -149,8 +149,8 @@ func (c *client) Whoami(ctx context.Context, token string) (string, error) {
}

// GetKeysJson retrieves the public keys from the registry
func (c *client) GetKeysJson(ctx context.Context) (signatures.KeysJson, error) {
var keys signatures.KeysJson
func (c *client) GetKeysJson(ctx context.Context) (signatures.Keys, error) {
var keys signatures.Keys

err := c.restClient.DoWithContext(
ctx,
Expand Down
36 changes: 30 additions & 6 deletions pkg/pm/resolution/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"golang.org/x/sync/errgroup"

"go.wpm.so/cli/pkg/pm/registry"
"go.wpm.so/cli/pkg/pm/signatures"
"go.wpm.so/cli/pkg/pm/wpmjson"
"go.wpm.so/cli/pkg/pm/wpmjson/manifest"
"go.wpm.so/cli/pkg/pm/wpmjson/types"
Expand All @@ -37,6 +38,7 @@ type Resolver struct {
rootConfig *wpmjson.Config
lockfile *wpmlock.Lockfile
client registry.Client
verifier *signatures.Verifier
}

func New(rootConfig *wpmjson.Config, lockfile *wpmlock.Lockfile, client registry.Client) *Resolver {
Expand All @@ -59,6 +61,12 @@ type fetchResult struct {
}

func (r *Resolver) Resolve(ctx context.Context, progress ProgressReporter, w io.Writer) (map[string]Node, error) {
keys, err := r.client.GetKeysJson(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch signing keys: %w", err)
}
r.verifier = signatures.New(keys)

resolved := make(map[string]Node)
queue := r.seedQueue()

Expand Down Expand Up @@ -94,7 +102,15 @@ func (r *Resolver) Resolve(ctx context.Context, progress ProgressReporter, w io.
}

func (r *Resolver) seedQueue() []dependencyRequest {
var queue []dependencyRequest
n := 0
if r.rootConfig.Dependencies != nil {
n += len(*r.rootConfig.Dependencies)
}
if r.rootConfig.DevDependencies != nil {
n += len(*r.rootConfig.DevDependencies)
}

queue := make([]dependencyRequest, 0, n)
if r.rootConfig.Dependencies != nil {
for name, version := range *r.rootConfig.Dependencies {
queue = append(queue, dependencyRequest{name: name, version: version, requestor: "<root>"})
Expand All @@ -108,21 +124,26 @@ func (r *Resolver) seedQueue() []dependencyRequest {
return queue
}

type requestKey struct {
name string
version string
}

// dedupeRequests drops requests already satisfied at the same version
// and folds identical name@version pairs in this iteration into one entry.
func dedupeRequests(queue []dependencyRequest, resolved map[string]Node) map[string]dependencyRequest {
uniqueRequests := make(map[string]dependencyRequest)
func dedupeRequests(queue []dependencyRequest, resolved map[string]Node) map[requestKey]dependencyRequest {
unique := make(map[requestKey]dependencyRequest, len(queue))
for _, req := range queue {
if exists, ok := resolved[req.name]; ok && exists.Version == req.version {
continue
}
uniqueRequests[req.name+"@"+req.version] = req
unique[requestKey{req.name, req.version}] = req
}
return uniqueRequests
return unique
}

// fetchAll fetches metadata for every request concurrently and returns the collected results.
func (r *Resolver) fetchAll(ctx context.Context, requests map[string]dependencyRequest, progress ProgressReporter, w io.Writer) ([]fetchResult, error) {
func (r *Resolver) fetchAll(ctx context.Context, requests map[requestKey]dependencyRequest, progress ProgressReporter, w io.Writer) ([]fetchResult, error) {
results := make(chan fetchResult, len(requests))
g, gtx := errgroup.WithContext(ctx)
g.SetLimit(16)
Expand All @@ -137,6 +158,9 @@ func (r *Resolver) fetchAll(ctx context.Context, requests map[string]dependencyR
if err != nil {
return fmt.Errorf("failed to fetch metadata for %s@%s required by %s: %w", req.name, req.version, req.requestor, err)
}
if err := r.verifier.Verify(manifest); err != nil {
return fmt.Errorf("signature verification failed for %s@%s required by %s: %w", req.name, req.version, req.requestor, err)
}
results <- fetchResult{req: req, manifest: manifest}
return nil
})
Expand Down
Loading
Loading