@@ -18,9 +18,12 @@ package main
1818
1919import (
2020 "bufio"
21+ "crypto/sha256"
2122 "encoding/json"
2223 "fmt"
24+ "io"
2325 "net"
26+ "net/http/httputil"
2427 "os"
2528 "os/exec"
2629 "strings"
@@ -85,11 +88,88 @@ func composeCommand() *cobra.Command {
8588 Args : cobra .ExactArgs (1 ),
8689 }
8790
88- c .AddCommand (upCmd , downCmd , stopCmd )
89- c .AddCommand (metadataCommand (upCmd , downCmd , stopCmd ))
91+ // pull is the image-distribution command: compose invokes it during the
92+ // image phase (up) and on `docker compose pull`, passing the identity of
93+ // the service image and the state of the local daemon cache. None of its
94+ // flags is required: they are injected by compose, not declared by the
95+ // user under provider.options.
96+ pullCmd := & cobra.Command {
97+ Use : "pull" ,
98+ Run : pull ,
99+ Args : cobra .ExactArgs (1 ),
100+ }
101+ pullCmd .Flags ().String ("image" , "" , "Image reference as compose resolved it" )
102+ pullCmd .Flags ().String ("digest" , "" , "Image ID in the local daemon cache, when present" )
103+ pullCmd .Flags ().String ("created" , "" , "Creation time of the local cache entry, when present" )
104+ pullCmd .Flags ().String ("source" , "" , "Authority verdict: local (sync from the daemon) or registry (resolve upstream)" )
105+ pullCmd .Flags ().String ("policy" , "" , "missing (a usable version suffices) or always (ensure freshness)" )
106+
107+ c .AddCommand (upCmd , downCmd , stopCmd , pullCmd )
108+ c .AddCommand (metadataCommand (upCmd , downCmd , stopCmd , pullCmd ))
90109 return c
91110}
92111
112+ // pull demonstrates the image-distribution contract. A real provider would
113+ // compare the announced digest/created with its bookkeeping and either
114+ // resolve the reference upstream (source=registry) or request the local
115+ // bytes (source=local). This demo requests the stream whenever
116+ // PROVIDER_PULL_MARKER is set, hashes it, and records the outcome there.
117+ func pull (cmd * cobra.Command , args []string ) {
118+ image , _ := cmd .Flags ().GetString ("image" )
119+ digest , _ := cmd .Flags ().GetString ("digest" )
120+ created , _ := cmd .Flags ().GetString ("created" )
121+ source , _ := cmd .Flags ().GetString ("source" )
122+ policy , _ := cmd .Flags ().GetString ("policy" )
123+
124+ emit := func (kind , message string ) {
125+ payload , _ := json .Marshal (map [string ]string {"type" : kind , "message" : message })
126+ fmt .Println (string (payload ))
127+ }
128+ emit ("info" , fmt .Sprintf ("ensuring image %s (source=%s, policy=%s)" , image , source , policy ))
129+
130+ marker := os .Getenv ("PROVIDER_PULL_MARKER" )
131+ if marker == "" {
132+ // nothing to synchronize in the demo: a real registry-sourced provider
133+ // would pull the reference upstream here
134+ return
135+ }
136+
137+ // Request the image bytes from the local daemon. The answer is one JSON
138+ // line, then — unless it carries an error — the tar as an HTTP/1.1
139+ // chunked body (readable with any stock chunked reader), whose zero
140+ // chunk marks a COMPLETE transfer.
141+ request , _ := json .Marshal (map [string ]string {"type" : "get-image" , "message" : image })
142+ fmt .Println (string (request ))
143+
144+ stdin := bufio .NewReader (os .Stdin )
145+ line , err := stdin .ReadString ('\n' )
146+ if err != nil {
147+ emit ("error" , "reading image-stream announce: " + err .Error ())
148+ os .Exit (1 )
149+ }
150+ var announce struct {
151+ Encoding string `json:"encoding"`
152+ Error string `json:"error"`
153+ }
154+ if err := json .Unmarshal ([]byte (line ), & announce ); err != nil || announce .Error != "" || announce .Encoding != "chunked" {
155+ emit ("error" , fmt .Sprintf ("image stream unavailable: %q (err %v)" , line , err ))
156+ os .Exit (1 )
157+ }
158+ h := sha256 .New ()
159+ n , err := io .Copy (h , httputil .NewChunkedReader (stdin ))
160+ if err != nil {
161+ emit ("error" , "image stream truncated: " + err .Error ())
162+ os .Exit (1 )
163+ }
164+ record := fmt .Sprintf ("image=%s digest=%s created=%s source=%s policy=%s sha256=%x bytes=%d\n " ,
165+ image , digest , created , source , policy , h .Sum (nil ), n )
166+ if err := os .WriteFile (marker , []byte (record ), 0o600 ); err != nil {
167+ emit ("error" , "writing marker: " + err .Error ())
168+ os .Exit (1 )
169+ }
170+ emit ("info" , fmt .Sprintf ("image received (%d bytes)" , n ))
171+ }
172+
93173// serveDemoCommand is the detached helper process behind the
94174// publish-endpoint demonstration: a TCP server on the given address
95175// answering every connection with a fixed HTTP response, exiting on its own
@@ -227,23 +307,27 @@ func stop(_ *cobra.Command, _ []string) {
227307 }
228308}
229309
230- func metadataCommand (upCmd , downCmd , stopCmd * cobra.Command ) * cobra.Command {
310+ func metadataCommand (upCmd , downCmd , stopCmd , pullCmd * cobra.Command ) * cobra.Command {
231311 return & cobra.Command {
232312 Use : "metadata" ,
233313 Run : func (cmd * cobra.Command , _ []string ) {
234- metadata (upCmd , downCmd , stopCmd )
314+ metadata (upCmd , downCmd , stopCmd , pullCmd )
235315 },
236316 Args : cobra .NoArgs ,
237317 }
238318}
239319
240- func metadata (upCmd , downCmd , stopCmd * cobra.Command ) {
320+ func metadata (upCmd , downCmd , stopCmd , pullCmd * cobra.Command ) {
241321 metadata := ProviderMetadata {}
242322 metadata .Description = "Manage services on AwesomeCloud"
243323 metadata .Up = commandParameters (upCmd )
244324 metadata .Down = commandParameters (downCmd )
245325 stopParams := commandParameters (stopCmd )
246326 metadata .Stop = & stopParams
327+ // like stop, declaring the pull block is what opts the provider into
328+ // image distribution
329+ pullParams := commandParameters (pullCmd )
330+ metadata .Pull = & pullParams
247331 jsonMetadata , err := json .Marshal (metadata )
248332 if err != nil {
249333 panic (err )
@@ -271,6 +355,7 @@ type ProviderMetadata struct {
271355 Up CommandMetadata `json:"up"`
272356 Down CommandMetadata `json:"down"`
273357 Stop * CommandMetadata `json:"stop,omitempty"`
358+ Pull * CommandMetadata `json:"pull,omitempty"`
274359}
275360
276361type CommandMetadata struct {
0 commit comments