Skip to content
This repository was archived by the owner on Aug 14, 2020. It is now read-only.

Commit c46f33d

Browse files
committed
spec: add image tags
This patch introduces the concept of image tags. Since many ACEs (like rkt) wants to implement local caching I tried to define Image Tags considering this as a primary requirement. While an image is static since its labels are written in its manifest and changing them will change the image id, image tags are dynamic. In short words it adds another file that contains a map to convert a tag to another tag (alias) and/or expand a tag to a set of labels. It pratically adds a layer above current image discovery used to obtain the final set of labels to use to calculte `ac-discovery` URLs. Image Tags are defined to be per image name. This is needed to satisfy the need to locally cache them (since they should be cached and retrieved using a key and this key is the image name). This will be useful to fix different issues and enhancements. This patch: * Introduce Image Tags format. * Details the discovery process (since there's now an optional additional step) * Add a "tag" inside image dependencies in image manifest. * Change the app string format parsing function. It now defines the value after the ":" as a Tag instead of the version label. * Add code for doing tag resolution and labels merging (that can be used by an ACE). As a fallback, if no image tags data is provided, the version label will be set from the tag value. * The image tags fetching and verification logic is left to the ACE (like done for an ACI). -- Docker compatibility Actually docker2aci converts a docker image (squashing it) to an ACI and sets the version label to the image tag. This is a fast but not consisten way to docker tags since they can be dynamically changed. With this proposal an idea will be to convert docker tags to an Image Tags file (using the docker APIs). Docker tags are per repository and a repository is mapped to an ACI image name. So this should match the proposal that dfines imagetags being per image. Docker images don't have a version label but can have a label representing the docker image ID. So an image tag can point to that "dockerimageid" label. -- rkt related work For rkt, the changes to implement to satisfy image tags should be: * Image Tags fetching and validation * Labels Merging (calling imageTags.MergeTag providing the starting * labels and tag value) * Caching of Tags Data per app name * Use of imageTags.MergeTag before calling GetACI. * Removal of "latest" column from the store since it was a hack around * current spec default "latest" version. Some open points: * Since there're an additional layer and an additional file there's the need to: * inspect Image Tags data (like the current `rkt image list` there should be something like `rkt imagetags list`) * fetch/update them (they are automatically fetched using `rkt --no-store fetch imagename`, but this will also fetch an image). To just fetch/update ImageTags something like `rkt imagetags --no-store fetch {IMAGENAME|file|URL}` should be added. Providing and image name will use discovery, while the other will just fetch it from the provided file/URL. * Since rkt also accept fetching an image from a file or URL, doing this won't carry Image Tags information. So a successive attemps to run an image by it's image string can use a different image than the one that will be run using an image discovery returning Image Tags data (due to the fallback of setting the version label value to tag if no image tag data is available). So a command like the above `rkt imagetags fetch` is needed also for this reason.
1 parent 9bdac40 commit c46f33d

16 files changed

+681
-60
lines changed

actool/discover.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@
1515
package main
1616

1717
import (
18+
"crypto/tls"
1819
"encoding/json"
1920
"fmt"
21+
"net"
22+
"net/http"
23+
"net/url"
2024
"runtime"
2125
"strings"
26+
"time"
2227

2328
"github.com/appc/spec/discovery"
29+
"github.com/appc/spec/schema"
2430
)
2531

2632
var (
@@ -62,12 +68,37 @@ func runDiscover(args []string) (exit int) {
6268
if transportFlags.Insecure {
6369
insecure = discovery.InsecureTLS | discovery.InsecureHTTP
6470
}
71+
tagsEndpoints, attempts, err := discovery.DiscoverImageTags(*app, nil, insecure)
72+
if err != nil {
73+
stderr("error fetching endpoints for %s: %s", name, err)
74+
return 1
75+
}
76+
for _, a := range attempts {
77+
fmt.Printf("discover tags walk: prefix: %s error: %v\n", a.Prefix, a.Error)
78+
}
79+
if len(tagsEndpoints) != 0 {
80+
tags, err := fetchImageTags(tagsEndpoints[0].ImageTags, insecure)
81+
if err != nil {
82+
stderr("error fetching tags info: %s", err)
83+
return 1
84+
}
85+
// Merge tag labels
86+
app, err = app.MergeTag(tags)
87+
if err != nil {
88+
stderr("error resolving tags to labels: %s", err)
89+
return 1
90+
}
91+
} else {
92+
fmt.Printf("no discover tags found")
93+
}
94+
6595
eps, attempts, err := discovery.DiscoverACIEndpoints(*app, nil, insecure)
6696
if err != nil {
6797
stderr("error fetching endpoints for %s: %s", name, err)
6898
return 1
6999
}
70100
for _, a := range attempts {
101+
71102
fmt.Printf("discover endpoints walk: prefix: %s error: %v\n", a.Prefix, a.Error)
72103
}
73104
publicKeys, attempts, err := discovery.DiscoverPublicKeys(*app, nil, insecure)
@@ -104,3 +135,61 @@ func runDiscover(args []string) (exit int) {
104135

105136
return
106137
}
138+
139+
func fetchImageTags(urlStr string, insecure discovery.InsecureOption) (*schema.ImageTags, error) {
140+
t := &http.Transport{
141+
Proxy: http.ProxyFromEnvironment,
142+
Dial: func(n, a string) (net.Conn, error) {
143+
return net.DialTimeout(n, a, 5*time.Second)
144+
},
145+
}
146+
if insecure&discovery.InsecureTLS != 0 {
147+
t.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
148+
}
149+
client := &http.Client{
150+
Transport: t,
151+
}
152+
153+
fetch := func(scheme string) (res *http.Response, err error) {
154+
u, err := url.Parse(urlStr)
155+
if err != nil {
156+
return nil, err
157+
}
158+
u.Scheme = scheme
159+
urlStr := u.String()
160+
req, err := http.NewRequest("GET", urlStr, nil)
161+
if err != nil {
162+
return nil, err
163+
}
164+
res, err = client.Do(req)
165+
return
166+
}
167+
closeBody := func(res *http.Response) {
168+
if res != nil {
169+
res.Body.Close()
170+
}
171+
}
172+
res, err := fetch("https")
173+
if err != nil || res.StatusCode != http.StatusOK {
174+
if insecure&discovery.InsecureHTTP != 0 {
175+
closeBody(res)
176+
res, err = fetch("http")
177+
}
178+
}
179+
180+
if res != nil && res.StatusCode != http.StatusOK {
181+
err = fmt.Errorf("expected a 200 OK got %d", res.StatusCode)
182+
}
183+
184+
if err != nil {
185+
closeBody(res)
186+
return nil, err
187+
}
188+
189+
var tags *schema.ImageTags
190+
jd := json.NewDecoder(res.Body)
191+
jd.Decode(&tags)
192+
closeBody(res)
193+
194+
return tags, nil
195+
}

discovery/discovery.go

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,25 @@ type ACIEndpoint struct {
3737
ASC string
3838
}
3939

40+
type ImageTagsEndpoint struct {
41+
ImageTags string
42+
ASC string
43+
}
44+
4045
// A struct containing both discovered endpoints and keys. Used to avoid
4146
// function duplication (one for endpoints and one for keys, so to avoid two
4247
// doDiscover, two DiscoverWalkFunc)
4348
type discoveryData struct {
44-
ACIEndpoints []ACIEndpoint
45-
PublicKeys []string
49+
ACIEndpoints []ACIEndpoint
50+
PublicKeys []string
51+
ImageTagsEndpoints []ImageTagsEndpoint
4652
}
4753

4854
type ACIEndpoints []ACIEndpoint
4955

5056
type PublicKeys []string
5157

52-
const (
53-
defaultVersion = "latest"
54-
)
58+
type ImageTagsEndpoints []ImageTagsEndpoint
5559

5660
var (
5761
templateExpression = regexp.MustCompile(`{.*?}`)
@@ -128,9 +132,6 @@ func createTemplateVars(app App) []string {
128132

129133
func doDiscover(pre string, hostHeaders map[string]http.Header, app App, insecure InsecureOption) (*discoveryData, error) {
130134
app = *app.Copy()
131-
if app.Labels["version"] == "" {
132-
app.Labels["version"] = defaultVersion
133-
}
134135

135136
_, body, err := httpsOrHTTP(pre, hostHeaders, insecure)
136137
if err != nil {
@@ -165,6 +166,20 @@ func doDiscover(pre string, hostHeaders map[string]http.Header, app App, insecur
165166

166167
case "ac-discovery-pubkeys":
167168
dd.PublicKeys = append(dd.PublicKeys, m.uri)
169+
case "ac-discovery-tags":
170+
// Only name is used for tags discovery
171+
tplVars := []string{"{name}", app.Name.String()}
172+
// Ignore not handled variables as {ext} isn't already rendered.
173+
uri, _ := renderTemplate(m.uri, tplVars...)
174+
asc, ok := renderTemplate(uri, "{ext}", "aci.asc")
175+
if !ok {
176+
continue
177+
}
178+
tags, ok := renderTemplate(uri, "{ext}", "aci")
179+
if !ok {
180+
continue
181+
}
182+
dd.ImageTagsEndpoints = append(dd.ImageTagsEndpoints, ImageTagsEndpoint{ImageTags: tags, ASC: asc})
168183
}
169184
}
170185

@@ -175,6 +190,7 @@ func doDiscover(pre string, hostHeaders map[string]http.Header, app App, insecur
175190
// optionally will use HTTP if insecure is set. hostHeaders specifies the
176191
// header to apply depending on the host (e.g. authentication). Based on the
177192
// response of the discoverFn it will continue to recurse up the tree.
193+
// If no discovery data can be found an empty discoveryData will be returned.
178194
func DiscoverWalk(app App, hostHeaders map[string]http.Header, insecure InsecureOption, discoverFn DiscoverWalkFunc) (dd *discoveryData, err error) {
179195
parts := strings.Split(string(app.Name), "/")
180196
for i := range parts {
@@ -187,7 +203,7 @@ func DiscoverWalk(app App, hostHeaders map[string]http.Header, insecure Insecure
187203
}
188204
}
189205

190-
return nil, fmt.Errorf("discovery failed")
206+
return &discoveryData{}, nil
191207
}
192208

193209
// DiscoverWalkFunc can stop a DiscoverWalk by returning non-nil error.
@@ -232,10 +248,13 @@ func DiscoverACIEndpoints(app App, hostHeaders map[string]http.Header, insecure
232248
return nil, attempts, err
233249
}
234250

251+
if len(dd.ACIEndpoints) == 0 {
252+
return nil, attempts, fmt.Errorf("No ACI endpoints discovered")
253+
}
235254
return dd.ACIEndpoints, attempts, nil
236255
}
237256

238-
// DiscoverPublicKey will make HTTPS requests to find the ac-public-keys meta
257+
// DiscoverPublicKeys will make HTTPS requests to find the ac-discovery-pubkeys meta
239258
// tags and optionally will use HTTP if insecure is set. hostHeaders
240259
// specifies the header to apply depending on the host (e.g. authentication).
241260
// It will not give up until it has exhausted the path or found an public key.
@@ -253,5 +272,29 @@ func DiscoverPublicKeys(app App, hostHeaders map[string]http.Header, insecure In
253272
return nil, attempts, err
254273
}
255274

275+
if len(dd.PublicKeys) == 0 {
276+
return nil, attempts, fmt.Errorf("No public keys discovered")
277+
}
256278
return dd.PublicKeys, attempts, nil
257279
}
280+
281+
// DiscoverImageTags will make HTTPS requests to find the ac-discovery-imagetags meta
282+
// tags and optionally will use HTTP if insecure is set. hostHeaders
283+
// specifies the header to apply depending on the host (e.g. authentication).
284+
// It will not give up until it has exhausted the path or found an imagetag.
285+
func DiscoverImageTags(app App, hostHeaders map[string]http.Header, insecure InsecureOption) (ImageTagsEndpoints, []FailedAttempt, error) {
286+
testFn := func(pre string, dd *discoveryData, err error) error {
287+
if len(dd.ImageTagsEndpoints) != 0 {
288+
return errEnough
289+
}
290+
return nil
291+
}
292+
293+
attempts := []FailedAttempt{}
294+
dd, err := DiscoverWalk(app, hostHeaders, insecure, walker(&attempts, testFn))
295+
if err != nil && err != errEnough {
296+
return nil, attempts, err
297+
}
298+
299+
return dd.ImageTagsEndpoints, attempts, nil
300+
}

0 commit comments

Comments
 (0)