Skip to content

Commit d804360

Browse files
committed
cli/command/image: deprecate PushTrustedReference, move to trust
This function was shared between "trust" "image" and "plugin" packages, all of which needed the trust package, so move it there instead. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent c6f456b commit d804360

5 files changed

Lines changed: 155 additions & 116 deletions

File tree

cli/command/image/push.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,8 @@ To push the complete multi-platform image, remove the --platform flag.
139139

140140
defer responseBody.Close()
141141
if !opts.untrusted {
142-
// TODO PushTrustedReference currently doesn't respect `--quiet`
143-
return PushTrustedReference(ctx, dockerCli, repoInfo, ref, authConfig, responseBody)
142+
// TODO pushTrustedReference currently doesn't respect `--quiet`
143+
return pushTrustedReference(ctx, dockerCli, repoInfo, ref, authConfig, responseBody)
144144
}
145145

146146
if opts.quiet {

cli/command/image/trust.go

Lines changed: 7 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,14 @@ package image
33
import (
44
"context"
55
"encoding/hex"
6-
"encoding/json"
76
"fmt"
87
"io"
9-
"sort"
108

119
"github.com/distribution/reference"
1210
"github.com/docker/cli/cli/command"
1311
"github.com/docker/cli/cli/internal/jsonstream"
1412
"github.com/docker/cli/cli/streams"
1513
"github.com/docker/cli/cli/trust"
16-
"github.com/docker/docker/api/types"
1714
"github.com/docker/docker/api/types/image"
1815
registrytypes "github.com/docker/docker/api/types/registry"
1916
"github.com/docker/docker/registry"
@@ -55,120 +52,19 @@ func TrustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.Reposi
5552

5653
defer responseBody.Close()
5754

58-
return PushTrustedReference(ctx, cli, repoInfo, ref, authConfig, responseBody)
55+
return trust.PushTrustedReference(ctx, cli, repoInfo, ref, authConfig, responseBody, command.UserAgent())
5956
}
6057

6158
// PushTrustedReference pushes a canonical reference to the trust server.
6259
//
63-
//nolint:gocyclo
60+
// Deprecated: use [trust.PushTrustedReference] instead. this function was only used internally and will be removed in the next release.
6461
func PushTrustedReference(ctx context.Context, ioStreams command.Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error {
65-
// If it is a trusted push we would like to find the target entry which match the
66-
// tag provided in the function and then do an AddTarget later.
67-
notaryTarget := &client.Target{}
68-
// Count the times of calling for handleTarget,
69-
// if it is called more that once, that should be considered an error in a trusted push.
70-
cnt := 0
71-
handleTarget := func(msg jsonstream.JSONMessage) {
72-
cnt++
73-
if cnt > 1 {
74-
// handleTarget should only be called once. This will be treated as an error.
75-
return
76-
}
77-
78-
var pushResult types.PushResult
79-
err := json.Unmarshal(*msg.Aux, &pushResult)
80-
if err == nil && pushResult.Tag != "" {
81-
if dgst, err := digest.Parse(pushResult.Digest); err == nil {
82-
h, err := hex.DecodeString(dgst.Hex())
83-
if err != nil {
84-
notaryTarget = nil
85-
return
86-
}
87-
notaryTarget.Name = pushResult.Tag
88-
notaryTarget.Hashes = data.Hashes{string(dgst.Algorithm()): h}
89-
notaryTarget.Length = int64(pushResult.Size)
90-
}
91-
}
92-
}
93-
94-
var tag string
95-
switch x := ref.(type) {
96-
case reference.Canonical:
97-
return errors.New("cannot push a digest reference")
98-
case reference.NamedTagged:
99-
tag = x.Tag()
100-
default:
101-
// We want trust signatures to always take an explicit tag,
102-
// otherwise it will act as an untrusted push.
103-
if err := jsonstream.Display(ctx, in, ioStreams.Out()); err != nil {
104-
return err
105-
}
106-
_, _ = fmt.Fprintln(ioStreams.Err(), "No tag specified, skipping trust metadata push")
107-
return nil
108-
}
109-
110-
if err := jsonstream.Display(ctx, in, ioStreams.Out(), jsonstream.WithAuxCallback(handleTarget)); err != nil {
111-
return err
112-
}
113-
114-
if cnt > 1 {
115-
return errors.Errorf("internal error: only one call to handleTarget expected")
116-
}
117-
118-
if notaryTarget == nil {
119-
return errors.Errorf("no targets found, provide a specific tag in order to sign it")
120-
}
121-
122-
_, _ = fmt.Fprintln(ioStreams.Out(), "Signing and pushing trust metadata")
123-
124-
repo, err := trust.GetNotaryRepository(ioStreams.In(), ioStreams.Out(), command.UserAgent(), repoInfo, &authConfig, "push", "pull")
125-
if err != nil {
126-
return errors.Wrap(err, "error establishing connection to trust repository")
127-
}
128-
129-
// get the latest repository metadata so we can figure out which roles to sign
130-
_, err = repo.ListTargets()
131-
132-
switch err.(type) {
133-
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
134-
keys := repo.GetCryptoService().ListKeys(data.CanonicalRootRole)
135-
var rootKeyID string
136-
// always select the first root key
137-
if len(keys) > 0 {
138-
sort.Strings(keys)
139-
rootKeyID = keys[0]
140-
} else {
141-
rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey)
142-
if err != nil {
143-
return err
144-
}
145-
rootKeyID = rootPublicKey.ID()
146-
}
147-
148-
// Initialize the notary repository with a remotely managed snapshot key
149-
if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
150-
return trust.NotaryError(repoInfo.Name.Name(), err)
151-
}
152-
_, _ = fmt.Fprintf(ioStreams.Out(), "Finished initializing %q\n", repoInfo.Name.Name())
153-
err = repo.AddTarget(notaryTarget, data.CanonicalTargetsRole)
154-
case nil:
155-
// already initialized and we have successfully downloaded the latest metadata
156-
err = trust.AddToAllSignableRoles(repo, notaryTarget)
157-
default:
158-
return trust.NotaryError(repoInfo.Name.Name(), err)
159-
}
160-
161-
if err == nil {
162-
err = repo.Publish()
163-
}
164-
165-
if err != nil {
166-
err = errors.Wrapf(err, "failed to sign %s:%s", repoInfo.Name.Name(), tag)
167-
return trust.NotaryError(repoInfo.Name.Name(), err)
168-
}
62+
return pushTrustedReference(ctx, ioStreams, repoInfo, ref, authConfig, in)
63+
}
16964

170-
_, _ = fmt.Fprintf(ioStreams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag)
171-
return nil
65+
// pushTrustedReference pushes a canonical reference to the trust server.
66+
func pushTrustedReference(ctx context.Context, ioStreams command.Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error {
67+
return trust.PushTrustedReference(ctx, ioStreams, repoInfo, ref, authConfig, in, command.UserAgent())
17268
}
17369

17470
// trustedPull handles content trust pulling of an image

cli/command/plugin/push.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import (
66
"github.com/distribution/reference"
77
"github.com/docker/cli/cli"
88
"github.com/docker/cli/cli/command"
9-
"github.com/docker/cli/cli/command/image"
109
"github.com/docker/cli/cli/internal/jsonstream"
10+
"github.com/docker/cli/cli/trust"
1111
registrytypes "github.com/docker/docker/api/types/registry"
1212
"github.com/docker/docker/registry"
1313
"github.com/pkg/errors"
@@ -66,7 +66,7 @@ func runPush(ctx context.Context, dockerCli command.Cli, opts pushOptions) error
6666
defer responseBody.Close()
6767

6868
if !opts.untrusted {
69-
return image.PushTrustedReference(ctx, dockerCli, repoInfo, named, authConfig, responseBody)
69+
return trust.PushTrustedReference(ctx, dockerCli, repoInfo, named, authConfig, responseBody, command.UserAgent())
7070
}
7171

7272
return jsonstream.Display(ctx, responseBody, dockerCli.Out())

cli/command/trust/sign.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ func runSignImage(ctx context.Context, dockerCLI command.Cli, options signOption
107107
return err
108108
}
109109
defer responseBody.Close()
110-
return image.PushTrustedReference(ctx, dockerCLI, imgRefAndAuth.RepoInfo(), imgRefAndAuth.Reference(), authConfig, responseBody)
110+
return trust.PushTrustedReference(ctx, dockerCLI, imgRefAndAuth.RepoInfo(), imgRefAndAuth.Reference(), authConfig, responseBody, command.UserAgent())
111111
default:
112112
return err
113113
}

cli/trust/trust_push.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package trust
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"encoding/json"
7+
"fmt"
8+
"io"
9+
"sort"
10+
11+
"github.com/distribution/reference"
12+
"github.com/docker/cli/cli/internal/jsonstream"
13+
"github.com/docker/cli/cli/streams"
14+
"github.com/docker/docker/api/types"
15+
registrytypes "github.com/docker/docker/api/types/registry"
16+
"github.com/docker/docker/registry"
17+
"github.com/opencontainers/go-digest"
18+
"github.com/pkg/errors"
19+
"github.com/theupdateframework/notary/client"
20+
"github.com/theupdateframework/notary/tuf/data"
21+
)
22+
23+
// Streams is an interface which exposes the standard input and output streams.
24+
//
25+
// Same interface as [github.com/docker/cli/cli/command.Streams] but defined here to prevent a circular import.
26+
type Streams interface {
27+
In() *streams.In
28+
Out() *streams.Out
29+
Err() *streams.Out
30+
}
31+
32+
// PushTrustedReference pushes a canonical reference to the trust server.
33+
//
34+
//nolint:gocyclo
35+
func PushTrustedReference(ctx context.Context, ioStreams Streams, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader, userAgent string) error {
36+
// If it is a trusted push we would like to find the target entry which match the
37+
// tag provided in the function and then do an AddTarget later.
38+
notaryTarget := &client.Target{}
39+
// Count the times of calling for handleTarget,
40+
// if it is called more that once, that should be considered an error in a trusted push.
41+
cnt := 0
42+
handleTarget := func(msg jsonstream.JSONMessage) {
43+
cnt++
44+
if cnt > 1 {
45+
// handleTarget should only be called once. This will be treated as an error.
46+
return
47+
}
48+
49+
var pushResult types.PushResult
50+
err := json.Unmarshal(*msg.Aux, &pushResult)
51+
if err == nil && pushResult.Tag != "" {
52+
if dgst, err := digest.Parse(pushResult.Digest); err == nil {
53+
h, err := hex.DecodeString(dgst.Hex())
54+
if err != nil {
55+
notaryTarget = nil
56+
return
57+
}
58+
notaryTarget.Name = pushResult.Tag
59+
notaryTarget.Hashes = data.Hashes{string(dgst.Algorithm()): h}
60+
notaryTarget.Length = int64(pushResult.Size)
61+
}
62+
}
63+
}
64+
65+
var tag string
66+
switch x := ref.(type) {
67+
case reference.Canonical:
68+
return errors.New("cannot push a digest reference")
69+
case reference.NamedTagged:
70+
tag = x.Tag()
71+
default:
72+
// We want trust signatures to always take an explicit tag,
73+
// otherwise it will act as an untrusted push.
74+
if err := jsonstream.Display(ctx, in, ioStreams.Out()); err != nil {
75+
return err
76+
}
77+
_, _ = fmt.Fprintln(ioStreams.Err(), "No tag specified, skipping trust metadata push")
78+
return nil
79+
}
80+
81+
if err := jsonstream.Display(ctx, in, ioStreams.Out(), jsonstream.WithAuxCallback(handleTarget)); err != nil {
82+
return err
83+
}
84+
85+
if cnt > 1 {
86+
return errors.Errorf("internal error: only one call to handleTarget expected")
87+
}
88+
89+
if notaryTarget == nil {
90+
return errors.Errorf("no targets found, provide a specific tag in order to sign it")
91+
}
92+
93+
_, _ = fmt.Fprintln(ioStreams.Out(), "Signing and pushing trust metadata")
94+
95+
repo, err := GetNotaryRepository(ioStreams.In(), ioStreams.Out(), userAgent, repoInfo, &authConfig, "push", "pull")
96+
if err != nil {
97+
return errors.Wrap(err, "error establishing connection to trust repository")
98+
}
99+
100+
// get the latest repository metadata so we can figure out which roles to sign
101+
_, err = repo.ListTargets()
102+
103+
switch err.(type) {
104+
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
105+
keys := repo.GetCryptoService().ListKeys(data.CanonicalRootRole)
106+
var rootKeyID string
107+
// always select the first root key
108+
if len(keys) > 0 {
109+
sort.Strings(keys)
110+
rootKeyID = keys[0]
111+
} else {
112+
rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey)
113+
if err != nil {
114+
return err
115+
}
116+
rootKeyID = rootPublicKey.ID()
117+
}
118+
119+
// Initialize the notary repository with a remotely managed snapshot key
120+
if err := repo.Initialize([]string{rootKeyID}, data.CanonicalSnapshotRole); err != nil {
121+
return NotaryError(repoInfo.Name.Name(), err)
122+
}
123+
_, _ = fmt.Fprintf(ioStreams.Out(), "Finished initializing %q\n", repoInfo.Name.Name())
124+
err = repo.AddTarget(notaryTarget, data.CanonicalTargetsRole)
125+
case nil:
126+
// already initialized and we have successfully downloaded the latest metadata
127+
err = AddToAllSignableRoles(repo, notaryTarget)
128+
default:
129+
return NotaryError(repoInfo.Name.Name(), err)
130+
}
131+
132+
if err == nil {
133+
err = repo.Publish()
134+
}
135+
136+
if err != nil {
137+
err = errors.Wrapf(err, "failed to sign %s:%s", repoInfo.Name.Name(), tag)
138+
return NotaryError(repoInfo.Name.Name(), err)
139+
}
140+
141+
_, _ = fmt.Fprintf(ioStreams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag)
142+
return nil
143+
}

0 commit comments

Comments
 (0)