Signed-off-by: David Calavera <david.calavera@gmail.com>
| ... | ... |
@@ -50,6 +50,7 @@ type apiClient interface {
|
| 50 | 50 |
ImageList(options types.ImageListOptions) ([]types.Image, error) |
| 51 | 51 |
ImageLoad(input io.Reader) (io.ReadCloser, error) |
| 52 | 52 |
ImagePull(options types.ImagePullOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error) |
| 53 |
+ ImagePush(options types.ImagePushOptions, privilegeFunc lib.RequestPrivilegeFunc) (io.ReadCloser, error) |
|
| 53 | 54 |
ImageRemove(options types.ImageRemoveOptions) ([]types.ImageDelete, error) |
| 54 | 55 |
ImageSave(imageIDs []string) (io.ReadCloser, error) |
| 55 | 56 |
ImageTag(options types.ImageTagOptions) error |
| 56 | 57 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,36 @@ |
| 0 |
+package lib |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "io" |
|
| 4 |
+ "net/http" |
|
| 5 |
+ "net/url" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/api/types" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+// ImagePush request the docker host to push an image to a remote registry. |
|
| 11 |
+// It executes the privileged function if the operation is unauthorized |
|
| 12 |
+// and it tries one more time. |
|
| 13 |
+// It's up to the caller to handle the io.ReadCloser and close it properly. |
|
| 14 |
+func (cli *Client) ImagePush(options types.ImagePushOptions, privilegeFunc RequestPrivilegeFunc) (io.ReadCloser, error) {
|
|
| 15 |
+ query := url.Values{}
|
|
| 16 |
+ query.Set("tag", options.Tag)
|
|
| 17 |
+ |
|
| 18 |
+ resp, err := cli.tryImagePush(options.ImageID, query, options.RegistryAuth) |
|
| 19 |
+ if resp.statusCode == http.StatusUnauthorized {
|
|
| 20 |
+ newAuthHeader, privilegeErr := privilegeFunc() |
|
| 21 |
+ if privilegeErr != nil {
|
|
| 22 |
+ return nil, privilegeErr |
|
| 23 |
+ } |
|
| 24 |
+ resp, err = cli.tryImagePush(options.ImageID, query, newAuthHeader) |
|
| 25 |
+ } |
|
| 26 |
+ if err != nil {
|
|
| 27 |
+ return nil, err |
|
| 28 |
+ } |
|
| 29 |
+ return resp.body, nil |
|
| 30 |
+} |
|
| 31 |
+ |
|
| 32 |
+func (cli *Client) tryImagePush(imageID string, query url.Values, registryAuth string) (*serverResponse, error) {
|
|
| 33 |
+ headers := map[string][]string{"X-Registry-Auth": {registryAuth}}
|
|
| 34 |
+ return cli.post("/images/"+imageID+"/push", query, nil, headers)
|
|
| 35 |
+} |
| ... | ... |
@@ -3,10 +3,14 @@ package client |
| 3 | 3 |
import ( |
| 4 | 4 |
"errors" |
| 5 | 5 |
"fmt" |
| 6 |
- "net/url" |
|
| 6 |
+ "io" |
|
| 7 | 7 |
|
| 8 | 8 |
"github.com/docker/distribution/reference" |
| 9 |
+ "github.com/docker/docker/api/client/lib" |
|
| 10 |
+ "github.com/docker/docker/api/types" |
|
| 9 | 11 |
Cli "github.com/docker/docker/cli" |
| 12 |
+ "github.com/docker/docker/cliconfig" |
|
| 13 |
+ "github.com/docker/docker/pkg/jsonmessage" |
|
| 10 | 14 |
flag "github.com/docker/docker/pkg/mflag" |
| 11 | 15 |
"github.com/docker/docker/registry" |
| 12 | 16 |
) |
| ... | ... |
@@ -53,13 +57,30 @@ func (cli *DockerCli) CmdPush(args ...string) error {
|
| 53 | 53 |
return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to <user>/<repo> (ex: %s/%s)", username, repoInfo.LocalName)
|
| 54 | 54 |
} |
| 55 | 55 |
|
| 56 |
+ requestPrivilege := cli.registryAuthenticationPrivilegedFunc(repoInfo.Index, "push") |
|
| 56 | 57 |
if isTrusted() {
|
| 57 |
- return cli.trustedPush(repoInfo, tag, authConfig) |
|
| 58 |
+ return cli.trustedPush(repoInfo, tag, authConfig, requestPrivilege) |
|
| 58 | 59 |
} |
| 59 | 60 |
|
| 60 |
- v := url.Values{}
|
|
| 61 |
- v.Set("tag", tag)
|
|
| 61 |
+ return cli.imagePushPrivileged(authConfig, ref.Name(), tag, cli.out, requestPrivilege) |
|
| 62 |
+} |
|
| 63 |
+ |
|
| 64 |
+func (cli *DockerCli) imagePushPrivileged(authConfig cliconfig.AuthConfig, imageID, tag string, outputStream io.Writer, requestPrivilege lib.RequestPrivilegeFunc) error {
|
|
| 65 |
+ encodedAuth, err := authConfig.EncodeToBase64() |
|
| 66 |
+ if err != nil {
|
|
| 67 |
+ return err |
|
| 68 |
+ } |
|
| 69 |
+ options := types.ImagePushOptions{
|
|
| 70 |
+ ImageID: imageID, |
|
| 71 |
+ Tag: tag, |
|
| 72 |
+ RegistryAuth: encodedAuth, |
|
| 73 |
+ } |
|
| 74 |
+ |
|
| 75 |
+ responseBody, err := cli.client.ImagePush(options, requestPrivilege) |
|
| 76 |
+ if err != nil {
|
|
| 77 |
+ return err |
|
| 78 |
+ } |
|
| 79 |
+ defer responseBody.Close() |
|
| 62 | 80 |
|
| 63 |
- _, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+ref.Name()+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push")
|
|
| 64 |
- return err |
|
| 81 |
+ return jsonmessage.DisplayJSONMessagesStream(responseBody, outputStream, cli.outFd, cli.isTerminalOut) |
|
| 65 | 82 |
} |
| ... | ... |
@@ -380,20 +380,18 @@ func targetStream(in io.Writer) (io.WriteCloser, <-chan []target) {
|
| 380 | 380 |
return ioutils.NewWriteCloserWrapper(out, w.Close), targetChan |
| 381 | 381 |
} |
| 382 | 382 |
|
| 383 |
-func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig) error {
|
|
| 383 |
+func (cli *DockerCli) trustedPush(repoInfo *registry.RepositoryInfo, tag string, authConfig cliconfig.AuthConfig, requestPrivilege lib.RequestPrivilegeFunc) error {
|
|
| 384 | 384 |
streamOut, targetChan := targetStream(cli.out) |
| 385 | 385 |
|
| 386 |
- v := url.Values{}
|
|
| 387 |
- v.Set("tag", tag)
|
|
| 386 |
+ reqError := cli.imagePushPrivileged(authConfig, repoInfo.LocalName.Name(), tag, streamOut, requestPrivilege) |
|
| 388 | 387 |
|
| 389 |
- _, _, err := cli.clientRequestAttemptLogin("POST", "/images/"+repoInfo.LocalName.Name()+"/push?"+v.Encode(), nil, streamOut, repoInfo.Index, "push")
|
|
| 390 | 388 |
// Close stream channel to finish target parsing |
| 391 | 389 |
if err := streamOut.Close(); err != nil {
|
| 392 | 390 |
return err |
| 393 | 391 |
} |
| 394 | 392 |
// Check error from request |
| 395 |
- if err != nil {
|
|
| 396 |
- return err |
|
| 393 |
+ if reqError != nil {
|
|
| 394 |
+ return reqError |
|
| 397 | 395 |
} |
| 398 | 396 |
|
| 399 | 397 |
// Get target results |
| ... | ... |
@@ -187,6 +187,9 @@ type ImagePullOptions struct {
|
| 187 | 187 |
RegistryAuth string |
| 188 | 188 |
} |
| 189 | 189 |
|
| 190 |
+//ImagePushOptions holds information to push images. |
|
| 191 |
+type ImagePushOptions ImagePullOptions |
|
| 192 |
+ |
|
| 190 | 193 |
// ImageRemoveOptions holds parameters to remove images. |
| 191 | 194 |
type ImageRemoveOptions struct {
|
| 192 | 195 |
ImageID string |