1
1
package compat
2
2
3
3
import (
4
+ "context"
5
+ "encoding/json"
4
6
"fmt"
5
7
"io/ioutil"
6
8
"net/http"
@@ -10,11 +12,14 @@ import (
10
12
"github.com/containers/podman/v3/libpod"
11
13
"github.com/containers/podman/v3/pkg/api/handlers/utils"
12
14
"github.com/containers/podman/v3/pkg/auth"
15
+ "github.com/containers/podman/v3/pkg/channel"
13
16
"github.com/containers/podman/v3/pkg/domain/entities"
14
17
"github.com/containers/podman/v3/pkg/domain/infra/abi"
15
18
"github.com/containers/storage"
19
+ "github.com/docker/docker/pkg/jsonmessage"
16
20
"github.com/gorilla/schema"
17
21
"github.com/pkg/errors"
22
+ "github.com/sirupsen/logrus"
18
23
)
19
24
20
25
// PushImage is the handler for the compat http endpoint for pushing images.
@@ -82,6 +87,8 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
82
87
Password : password ,
83
88
Username : username ,
84
89
DigestFile : digestFile .Name (),
90
+ Quiet : true ,
91
+ Progress : make (chan types.ProgressProperties ),
85
92
}
86
93
if _ , found := r .URL .Query ()["tlsVerify" ]; found {
87
94
options .SkipTLSVerify = types .NewOptionalBool (! query .TLSVerify )
@@ -94,31 +101,103 @@ func PushImage(w http.ResponseWriter, r *http.Request) {
94
101
destination = imageName
95
102
}
96
103
97
- if err := imageEngine .Push (r .Context (), imageName , destination , options ); err != nil {
98
- if errors .Cause (err ) != storage .ErrImageUnknown {
99
- utils .ImageNotFound (w , imageName , errors .Wrapf (err , "failed to find image %s" , imageName ))
100
- return
104
+ errorWriter := channel .NewWriter (make (chan []byte ))
105
+ defer errorWriter .Close ()
106
+
107
+ statusWriter := channel .NewWriter (make (chan []byte ))
108
+ defer statusWriter .Close ()
109
+
110
+ runCtx , cancel := context .WithCancel (context .Background ())
111
+ var failed bool
112
+
113
+ go func () {
114
+ defer cancel ()
115
+
116
+ statusWriter .Write ([]byte (fmt .Sprintf ("The push refers to repository [%s]" , imageName )))
117
+
118
+ err := imageEngine .Push (runCtx , imageName , destination , options )
119
+ if err != nil {
120
+ if errors .Cause (err ) != storage .ErrImageUnknown {
121
+ errorWriter .Write ([]byte ("An image does not exist locally with the tag: " + imageName ))
122
+ } else {
123
+ errorWriter .Write ([]byte (err .Error ()))
124
+ }
101
125
}
126
+ }()
102
127
103
- utils .Error (w , "Something went wrong." , http .StatusBadRequest , errors .Wrapf (err , "error pushing image %q" , imageName ))
104
- return
128
+ flush := func () {
129
+ if flusher , ok := w .(http.Flusher ); ok {
130
+ flusher .Flush ()
131
+ }
105
132
}
106
133
107
- digestBytes , err := ioutil .ReadAll (digestFile )
108
- if err != nil {
109
- utils .Error (w , "Something went wrong." , http .StatusInternalServerError , errors .Wrap (err , "failed to read digest tmp file" ))
110
- return
111
- }
134
+ w .WriteHeader (http .StatusOK )
135
+ w .Header ().Add ("Content-Type" , "application/json" )
136
+ flush ()
112
137
113
- tag := query .Tag
114
- if tag == "" {
115
- tag = "latest"
116
- }
117
- respData := struct {
118
- Status string `json:"status"`
119
- }{
120
- Status : fmt .Sprintf ("%s: digest: %s size: null" , tag , string (digestBytes )),
121
- }
138
+ enc := json .NewEncoder (w )
139
+ enc .SetEscapeHTML (true )
140
+
141
+ loop: // break out of for/select infinite loop
142
+ for {
143
+ var report jsonmessage.JSONMessage
122
144
123
- utils .WriteJSON (w , http .StatusOK , & respData )
145
+ select {
146
+ case e := <- options .Progress :
147
+ switch e .Event {
148
+ case types .ProgressEventNewArtifact :
149
+ report .Status = "Preparing"
150
+ case types .ProgressEventRead :
151
+ report .Status = "Pushing"
152
+ report .Progress = & jsonmessage.JSONProgress {
153
+ Current : int64 (e .Offset ),
154
+ Total : e .Artifact .Size ,
155
+ }
156
+ case types .ProgressEventSkipped :
157
+ report .Status = "Layer already exists"
158
+ case types .ProgressEventDone :
159
+ report .Status = "Pushed"
160
+ }
161
+ report .ID = e .Artifact .Digest .Encoded ()[0 :12 ]
162
+ if err := enc .Encode (report ); err != nil {
163
+ errorWriter .Write ([]byte (err .Error ()))
164
+ }
165
+ flush ()
166
+ case e := <- statusWriter .Chan ():
167
+ report .Status = string (e )
168
+ if err := enc .Encode (report ); err != nil {
169
+ errorWriter .Write ([]byte (err .Error ()))
170
+ }
171
+ flush ()
172
+ case e := <- errorWriter .Chan ():
173
+ failed = true
174
+ report .Error = & jsonmessage.JSONError {
175
+ Message : string (e ),
176
+ }
177
+ report .ErrorMessage = string (e )
178
+ if err := enc .Encode (report ); err != nil {
179
+ logrus .Warnf ("Failed to json encode error %q" , err .Error ())
180
+ }
181
+ flush ()
182
+ case <- runCtx .Done ():
183
+ if ! failed {
184
+ digestBytes , err := ioutil .ReadAll (digestFile )
185
+ if err == nil {
186
+ tag := query .Tag
187
+ if tag == "" {
188
+ tag = "latest"
189
+ }
190
+ report .Status = fmt .Sprintf ("%s: digest: %s" , tag , string (digestBytes ))
191
+ if err := enc .Encode (report ); err != nil {
192
+ logrus .Warnf ("Failed to json encode error %q" , err .Error ())
193
+ }
194
+ flush ()
195
+ }
196
+ }
197
+ break loop // break out of for/select infinite loop
198
+ case <- r .Context ().Done ():
199
+ // Client has closed connection
200
+ break loop // break out of for/select infinite loop
201
+ }
202
+ }
124
203
}
0 commit comments