func parseApp(args []string) ([]string, *schema.RuntimeApp, error) { if len(args) == 0 { return nil, nil, nil } rtapp := schema.RuntimeApp{} // Parse first argument (image name) if h, err := types.NewHash(args[0]); err == nil { rtapp.Image.ID = *h rtapp.Name.Set(h.String()) // won't err } else if dapp, err := discovery.NewAppFromString(args[0]); err == nil { rtapp.Image.Name = &dapp.Name rtapp.Name.Set(path.Base(dapp.Name.String())) // won't err here if ll, err := types.LabelsFromMap(dapp.Labels); err != nil { return args, nil, err } else { rtapp.Image.Labels = ll } } else { return args, nil, err } fl := flag.NewFlagSet(args[0], flag.ExitOnError) fl.Var(&rtapp.Name, "name", "App name") fl.Var((*AnnotationsFlag)(&rtapp.Annotations), "a", "Add annotation (NAME=VALUE)") fl.Var((*MountsFlag)(&rtapp.Mounts), "m", "Mount volume (VOLUME[:MOUNTPOINT])") // TODO: app override fl.Parse(args[1:]) return fl.Args(), &rtapp, nil }
func runDiscover(args []string) (exit int) { if len(args) < 1 { stderr("discover: at least one name required") } for _, name := range args { app, err := discovery.NewAppFromString(name) if app.Labels["os"] == "" { app.Labels["os"] = runtime.GOOS } if app.Labels["arch"] == "" { app.Labels["arch"] = runtime.GOARCH } if err != nil { stderr("%s: %s", name, err) return 1 } eps, attempts, err := discovery.DiscoverEndpoints(*app, transportFlags.Insecure) if err != nil { stderr("error fetching %s: %s", name, err) return 1 } for _, a := range attempts { fmt.Printf("discover walk: prefix: %s error: %v\n", a.Prefix, a.Error) } for _, aciEndpoint := range eps.ACIEndpoints { fmt.Printf("ACI: %s, ASC: %s\n", aciEndpoint.ACI, aciEndpoint.ASC) } if len(eps.Keys) > 0 { fmt.Println("Keys: " + strings.Join(eps.Keys, ",")) } } return }
func (n ACFullname) LatestVersion() (string, error) { app, err := discovery.NewAppFromString(n.Name() + ":latest") if app.Labels["os"] == "" { app.Labels["os"] = "linux" } if app.Labels["arch"] == "" { app.Labels["arch"] = "amd64" } endpoint, _, err := discovery.DiscoverEndpoints(*app, nil, false) if err != nil { return "", errors.Annotate(err, "Latest discovery fail") } r, _ := regexp.Compile(`^(\d+\.)?(\d+\.)?(\*|\d+)(\-[\dA-Za-z]+){0,1}$`) url := getRedirectForLatest(endpoint.ACIEndpoints[0].ACI) logs.WithField("url", url).Debug("latest verion url") for _, part := range strings.Split(url, "/") { if r.Match([]byte(part)) { return part, nil } } return "", errors.New("No latest version found") }
// metaDiscoverPubKeyLocations discovers the locations of public keys through ACDiscovery by applying prefix as an ACApp func (m *Manager) metaDiscoverPubKeyLocations(prefix string) ([]string, error) { app, err := discovery.NewAppFromString(prefix) if err != nil { return nil, err } hostHeaders := config.ResolveAuthPerHost(m.AuthPerHost) insecure := discovery.InsecureNone if m.InsecureAllowHTTP { insecure = insecure | discovery.InsecureHttp } if m.InsecureSkipTLSCheck { insecure = insecure | discovery.InsecureTls } ep, attempts, err := discovery.DiscoverPublicKeys(*app, hostHeaders, insecure) if err != nil { return nil, err } if m.Debug { for _, a := range attempts { log.PrintE(fmt.Sprintf("meta tag 'ac-discovery-pubkeys' not found on %s", a.Prefix), a.Error) } } return ep.Keys, nil }
func (n ACFullname) LatestVersion() (string, error) { app, err := discovery.NewAppFromString(n.Name() + ":latest") if app.Labels["os"] == "" { app.Labels["os"] = "linux" } if app.Labels["arch"] == "" { app.Labels["arch"] = "amd64" } endpoints, _, err := discovery.DiscoverACIEndpoints(*app, nil, discovery.InsecureTLS|discovery.InsecureHTTP) //TODO support security if err != nil { return "", errors.Annotate(err, "Latest discovery fail") } r, _ := regexp.Compile(`^\d+(.\d+){0,2}(-[\.\-\dA-Za-z]+){0,1}$`) // TODO this is nexus specific if len(endpoints) == 0 { return "", errs.WithF(data.WithField("aci", string(n)), "Discovery does not give an endpoint to check latest version") } url := getRedirectForLatest(endpoints[0].ACI) logs.WithField("url", url).Debug("latest verion url") for _, part := range strings.Split(url, "/") { if r.Match([]byte(part)) { return part, nil } } return "", errors.New("No latest version found") }
func TestNewAppcFromAppString(t *testing.T) { tests := []struct { appcRef string expected string }{ { "example.com/app01", "cimd:appc:v=0:example.com/app01", }, { "example.com/app01:v1.0.0", "cimd:appc:v=0:example.com/app01?version=v1.0.0", }, { "example.com/app01,version=v1.0.0", "cimd:appc:v=0:example.com/app01?version=v1.0.0", }, { "example.com/app01,version=v1.0.0,label01=?&*/", "cimd:appc:v=0:example.com/app01?label01=%3F%26%2A%2F&version=v1.0.0", }, { "example-app01", "cimd:appc:v=0:example-app01", }, { "example-app01:v1.0", "cimd:appc:v=0:example-app01?version=v1.0", }, } for _, tt := range tests { app, err := discovery.NewAppFromString(tt.appcRef) if err != nil { t.Fatalf("unexpected error: %v", err) } appc := NewAppcFromApp(app) if err != nil { t.Fatalf("unexpected error: %v", err) } u, err := url.Parse(tt.expected) if err != nil { t.Fatalf("unexpected error: %v", err) } td, err := NewAppc(u) if err != nil { t.Fatalf("unexpected error: %v", err) } if !appc.Equals(td) { t.Errorf("expected identical distribution but got %q != %q", td.CIMD().String(), appc.CIMD().String()) continue } } }
func TestApp(t *testing.T) { tests := []struct { uriStr string out string }{ { "cimd:appc:v=0:example.com/app01", "example.com/app01", }, { "cimd:appc:v=0:example.com/app01?version=v1.0.0", "example.com/app01:v1.0.0", }, { "cimd:appc:v=0:example.com/app01?version=v1.0.0", "example.com/app01,version=v1.0.0", }, { "cimd:appc:v=0:example.com/app01?label01=%3F%26%2A%2F&version=v1.0.0", "example.com/app01,version=v1.0.0,label01=?&*/", }, { "cimd:appc:v=0:example-app01", "example-app01", }, { "cimd:appc:v=0:example-app01?version=v1.0", "example-app01:v1.0", }, } for _, tt := range tests { u, err := url.Parse(tt.uriStr) if err != nil { t.Fatalf("unexpected error: %v", err) } appc, err := NewAppc(u) if err != nil { t.Fatalf("unexpected error: %v", err) } app := appc.(*Appc).App() expectedApp, err := discovery.NewAppFromString(tt.out) if err != nil { t.Fatalf("unexpected error: %v", err) } if !reflect.DeepEqual(app, expectedApp) { t.Fatalf("expected app %s, but got %q", expectedApp.String(), app.String()) } } }
// RetrieveImage can be used to retrieve a remote image, and optionally discover // an image based on the App Container Image Discovery specification. Supports // handling local images as well as func RetrieveImage(imageUri string, insecure bool) (ReaderCloserSeeker, error) { u, err := url.Parse(imageUri) if err != nil { return nil, err } switch u.Scheme { case "file": // for file:// urls, just load the file and return it return os.Open(u.Path) case "http", "https": // Handle HTTP retrievals, wrapped with a tempfile that cleans up. resp, err := Client.Get(imageUri) if err != nil { return nil, err } defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: default: return nil, fmt.Errorf("HTTP %d on retrieving %q", resp.StatusCode, imageUri) } return newTempReader(resp.Body) case "": app, err := discovery.NewAppFromString(imageUri) if err != nil { return nil, err } endpoints, _, err := discovery.DiscoverEndpoints(*app, insecure) if err != nil { return nil, err } for _, ep := range endpoints.ACIEndpoints { r, err := RetrieveImage(ep.ACI, insecure) if err != nil { continue } // FIXME should also attempt to validate the signature return r, nil } return nil, fmt.Errorf("failed to find a valid image for %q", imageUri) default: return nil, fmt.Errorf("%q scheme not supported", u.Scheme) } }
func runAddDep(cmd *cobra.Command, args []string) (exit int) { if len(args) == 0 { cmd.Usage() return 1 } if len(args) != 1 { stderr("dependency add: incorrect number of arguments") return 1 } if debug { stderr("Adding dependency %q", args[0]) } app, err := discovery.NewAppFromString(args[0]) if err != nil { stderr("dependency add: couldn't parse dependency name: %v", err) return 1 } appcLabels := types.Labels(labels) for name, value := range app.Labels { if _, ok := appcLabels.Get(string(name)); ok { stderr("multiple %s labels specified", name) return 1 } appcLabels = append(appcLabels, types.Label{ Name: name, Value: value, }) } var hash *types.Hash if imageId != "" { var err error hash, err = types.NewHash(imageId) if err != nil { stderr("dependency add: couldn't parse image ID: %v", err) return 1 } } err = newACBuild().AddDependency(app.Name, hash, appcLabels, size) if err != nil { stderr("dependency add: %v", err) return getErrorCode(err) } return 0 }
func tryAppFromString(location string) *discovery.App { if app, err := discovery.NewAppFromString(location); err != nil { return nil } else { if app.Labels["os"] == "" { app.Labels["os"] = runtime.GOOS } if app.Labels["arch"] == "" { app.Labels["arch"] = runtime.GOARCH } return app } }
func runDiscover(args []string) (exit int) { if len(args) < 1 { stderr("discover: at least one name required") } for _, name := range args { app, err := discovery.NewAppFromString(name) if app.Labels["os"] == "" { app.Labels["os"] = runtime.GOOS } if app.Labels["arch"] == "" { app.Labels["arch"] = runtime.GOARCH } if err != nil { stderr("%s: %s", name, err) return 1 } insecure := discovery.InsecureNone if transportFlags.Insecure { insecure = discovery.InsecureTls | discovery.InsecureHttp } eps, attempts, err := discovery.DiscoverEndpoints(*app, nil, insecure) if err != nil { stderr("error fetching %s: %s", name, err) return 1 } for _, a := range attempts { fmt.Printf("discover walk: prefix: %s error: %v\n", a.Prefix, a.Error) } if outputJson { jsonBytes, err := json.MarshalIndent(&eps, "", " ") if err != nil { stderr("error generating JSON: %s", err) return 1 } fmt.Println(string(jsonBytes)) } else { for _, aciEndpoint := range eps.ACIEndpoints { fmt.Printf("ACI: %s, ASC: %s\n", aciEndpoint.ACI, aciEndpoint.ASC) } if len(eps.Keys) > 0 { fmt.Println("Keys: " + strings.Join(eps.Keys, ",")) } } } return }
func (s Service) discoverPod(name cntspec.ACFullname) []cntspec.ACFullname { logAci := s.log.WithField("pod", name) app, err := discovery.NewAppFromString(name.String()) if app.Labels["os"] == "" { app.Labels["os"] = "linux" } if app.Labels["arch"] == "" { app.Labels["arch"] = "amd64" } endpoint, _, err := discovery.DiscoverEndpoints(*app, false) if err != nil { logAci.WithError(err).Fatal("pod discovery failed") } url := endpoint.ACIEndpoints[0].ACI url = strings.Replace(url, "=aci", "=pod", 1) // TODO this is nexus specific logUrl := logAci.WithField("url", url) response, err := http.Get(url) if err != nil { logUrl.WithError(err).Fatal("Cannot get pod manifest content") return nil } else { if response.StatusCode != 200 { logUrl.WithField("status_code", response.StatusCode).WithField("status_message", response.Status). Fatal("Receive response error for discovery") } defer response.Body.Close() content, err := ioutil.ReadAll(response.Body) if err != nil { logUrl.WithError(err).Fatal("Cannot read pod manifest content") } tmpMap := make(map[string][]cntspec.ACFullname, 1) if err := s.podManifestToMap(tmpMap, content); err != nil { logUrl.WithError(err).Fatal("Cannot read pod content") } acis := tmpMap[name.Name()] if acis == nil { logUrl.Fatal("Discovered pod name does not match requested") } return acis } }
func getStoreKeyFromApp(s *imagestore.Store, img string) (string, error) { app, err := discovery.NewAppFromString(img) if err != nil { return "", errwrap.Wrap(fmt.Errorf("cannot parse the image name %q", img), err) } labels, err := types.LabelsFromMap(app.Labels) if err != nil { return "", errwrap.Wrap(fmt.Errorf("invalid labels in the image %q", img), err) } key, err := s.GetACI(app.Name, labels) if err != nil { switch err.(type) { case imagestore.ACINotFoundError: return "", err default: return "", errwrap.Wrap(fmt.Errorf("cannot find image %q", img), err) } } return key, nil }
func parseImageName(name string) (types.ACIdentifier, types.Labels, error) { app, err := discovery.NewAppFromString(name) if err != nil { return "", nil, errors.Trace(err) } if app.Labels["os"] == "" { app.Labels["os"] = runtime.GOOS } if app.Labels["arch"] == "" { app.Labels["arch"] = runtime.GOARCH } labels, err := types.LabelsFromMap(app.Labels) if err != nil { return "", nil, errors.Trace(err) } return app.Name, labels, nil }
func newAppBundle(name string) (*appBundle, error) { app, err := discovery.NewAppFromString(name) if err != nil { return nil, errwrap.Wrap(fmt.Errorf("invalid image name %q", name), err) } if _, ok := app.Labels["arch"]; !ok { app.Labels["arch"] = runtime.GOARCH } if _, ok := app.Labels["os"]; !ok { app.Labels["os"] = runtime.GOOS } if err := types.IsValidOSArch(app.Labels, stage0.ValidOSArch); err != nil { return nil, errwrap.Wrap(fmt.Errorf("invalid image name %q", name), err) } bundle := &appBundle{ App: app, Str: name, } return bundle, nil }
// NewAppc returns an Appc distribution from an Appc distribution URI func NewAppc(u *url.URL) (Distribution, error) { c, err := parseCIMD(u) if err != nil { return nil, fmt.Errorf("cannot parse URI: %q: %v", u.String(), err) } if c.Type != TypeAppc { return nil, fmt.Errorf("wrong distribution type: %q", c.Type) } appcStr := c.Data for n, v := range u.Query() { appcStr += fmt.Sprintf(",%s=%s", n, v[0]) } app, err := discovery.NewAppFromString(appcStr) if err != nil { return nil, fmt.Errorf("wrong appc image string %q: %v", u.String(), err) } return NewAppcFromApp(app), nil }
func (cnt *Img) processFrom() { if cnt.manifest.From != "" { log.Get().Info("Prepare rootfs from " + cnt.manifest.From) app, err := discovery.NewAppFromString(string(cnt.manifest.From)) if app.Labels["os"] == "" { app.Labels["os"] = "linux" } if app.Labels["arch"] == "" { app.Labels["arch"] = "amd64" } endpoint, _, err := discovery.DiscoverEndpoints(*app, false) if err != nil { panic(err) } url := endpoint.ACIEndpoints[0].ACI aciPath := config.GetConfig().AciPath + "/" + string(cnt.manifest.From) if _, err := os.Stat(aciPath + "/image.aci"); cnt.args.ForceUpdate || os.IsNotExist(err) { if err := os.MkdirAll(aciPath, 0755); err != nil { log.Get().Panic(err) } if err = utils.ExecCmd("wget", "-O", aciPath+"/image.aci", url); err != nil { os.Remove(aciPath + "/image.aci") log.Get().Panic("Cannot download from image", err) } } else { log.Get().Info("Image " + cnt.manifest.From + " Already exists locally, will not be downloaded") } utils.ExecCmd("tar", "xpf", aciPath+"/image.aci", "-C", cnt.target) // utils.ExecCmd("rkt", "--insecure-skip-verify=true", "fetch", cnt.manifest.From) // utils.ExecCmd("rkt", "image", "export", "--overwrite", cnt.manifest.From, cnt.target + "/from.aci") // utils.ExecCmd("tar", "xf", cnt.target + "/from.aci", "-C", cnt.target) // os.Remove(cnt.target + "/from.aci") } }
func (a *ACBuild) beginFromRemoteImage(start string, insecure bool) error { app, err := discovery.NewAppFromString(start) if err != nil { return err } labels, err := types.LabelsFromMap(app.Labels) if err != nil { return err } tmpDepStoreTarPath, err := ioutil.TempDir("", "acbuild-begin-tar") if err != nil { return err } defer os.RemoveAll(tmpDepStoreTarPath) tmpDepStoreExpandedPath, err := ioutil.TempDir("", "acbuild-begin-expanded") if err != nil { return err } defer os.RemoveAll(tmpDepStoreExpandedPath) reg := registry.Registry{ DepStoreTarPath: tmpDepStoreTarPath, DepStoreExpandedPath: tmpDepStoreExpandedPath, Insecure: insecure, Debug: a.Debug, } err = reg.Fetch(app.Name, labels, 0, false) if err != nil { if urlerr, ok := err.(*url.Error); ok { if operr, ok := urlerr.Err.(*net.OpError); ok { if dnserr, ok := operr.Err.(*net.DNSError); ok { if dnserr.Err == "no such host" { return fmt.Errorf("unknown host when fetching image, check your connection and local file paths must start with '/' or '.'") } } } } return err } files, err := ioutil.ReadDir(tmpDepStoreTarPath) if err != nil { return err } if len(files) != 1 { var filelist string for _, file := range files { if filelist == "" { filelist = file.Name() } else { filelist = filelist + ", " + file.Name() } } panic("unexpected number of files in store after download: " + filelist) } return util.ExtractImage(path.Join(tmpDepStoreTarPath, files[0].Name()), a.CurrentACIPath, nil) }
// DistFromImageString return the distribution for the given input image string func DistFromImageString(is string) (dist.Distribution, error) { u, err := url.Parse(is) if err != nil { return nil, errwrap.Wrap(fmt.Errorf("failed to parse image url %q", is), err) } // Convert user friendly image string names to internal distribution URIs // file:///full/path/to/aci/file.aci -> archive:aci:file%3A%2F%2F%2Ffull%2Fpath%2Fto%2Faci%2Ffile.aci switch u.Scheme { case "": // no scheme given, hence it is an appc image name or path appImageType := guessAppcOrPath(is, []string{schema.ACIExtension}) switch appImageType { case imageStringName: app, err := discovery.NewAppFromString(is) if err != nil { return nil, fmt.Errorf("invalid appc image string %q: %v", is, err) } return dist.NewAppcFromApp(app), nil case imageStringPath: absPath, err := filepath.Abs(is) if err != nil { return nil, errwrap.Wrap(fmt.Errorf("failed to get an absolute path for %q", is), err) } is = "file://" + absPath // given a file:// image string, call this function again to return an ACI distribution return DistFromImageString(is) default: return nil, fmt.Errorf("invalid image string type %q", appImageType) } case "file", "http", "https": // An ACI archive with any transport type (file, http, s3 etc...) and final aci extension if filepath.Ext(u.Path) == schema.ACIExtension { dist, err := dist.NewACIArchiveFromTransportURL(u) if err != nil { return nil, fmt.Errorf("archive distribution creation error: %v", err) } return dist, nil } case "docker": // Accept both docker: and docker:// uri dockerStr := is if strings.HasPrefix(dockerStr, "docker://") { dockerStr = strings.TrimPrefix(dockerStr, "docker://") } else if strings.HasPrefix(dockerStr, "docker:") { dockerStr = strings.TrimPrefix(dockerStr, "docker:") } dist, err := dist.NewDockerFromString(dockerStr) if err != nil { return nil, fmt.Errorf("docker distribution creation error: %v", err) } return dist, nil case dist.Scheme: // cimd return dist.Parse(is) default: // any other scheme is a an appc image name, i.e. "my-app:v1.0" app, err := discovery.NewAppFromString(is) if err != nil { return nil, fmt.Errorf("invalid appc image string %q: %v", is, err) } return dist.NewAppcFromApp(app), nil } return nil, fmt.Errorf("invalid image string %q", is) }
func runDiscover(args []string) (exit int) { if len(args) < 1 { stderr("discover: at least one name required") } for _, name := range args { app, err := discovery.NewAppFromString(name) if app.Labels["os"] == "" { app.Labels["os"] = runtime.GOOS } if app.Labels["arch"] == "" { app.Labels["arch"] = runtime.GOARCH } if err != nil { stderr("%s: %s", name, err) return 1 } insecure := discovery.InsecureNone if transportFlags.Insecure { insecure = discovery.InsecureTLS | discovery.InsecureHTTP } eps, attempts, err := discovery.DiscoverACIEndpoints(*app, nil, insecure) if err != nil { stderr("error fetching endpoints for %s: %s", name, err) return 1 } for _, a := range attempts { fmt.Printf("discover endpoints walk: prefix: %s error: %v\n", a.Prefix, a.Error) } publicKeys, attempts, err := discovery.DiscoverPublicKeys(*app, nil, insecure) if err != nil { stderr("error fetching public keys for %s: %s", name, err) return 1 } for _, a := range attempts { fmt.Printf("discover public keys walk: prefix: %s error: %v\n", a.Prefix, a.Error) } type discoveryData struct { ACIEndpoints []discovery.ACIEndpoint PublicKeys []string } if outputJson { dd := discoveryData{ACIEndpoints: eps, PublicKeys: publicKeys} jsonBytes, err := json.MarshalIndent(dd, "", " ") if err != nil { stderr("error generating JSON: %s", err) return 1 } fmt.Println(string(jsonBytes)) } else { for _, aciEndpoint := range eps { fmt.Printf("ACI: %s, ASC: %s\n", aciEndpoint.ACI, aciEndpoint.ASC) } if len(publicKeys) > 0 { fmt.Println("PublicKeys: " + strings.Join(publicKeys, ",")) } } } return }