func TestDistFromImageString(t *testing.T) { relPath1 := "some/relative/../path/with/dots/file.aci" absPath1, err := filepath.Abs(relPath1) if err != nil { t.Fatalf("unexpected error: %v", err) } relPath2 := "some/relative/../path/with/dots/file" absPath2, err := filepath.Abs(relPath2) if err != nil { t.Fatalf("unexpected error: %v", err) } tests := []struct { in string distString string err error }{ // Appc { "example.com/app01", "cimd:appc:v=0:example.com/app01", nil, }, { "example.com/app01:v1.0.0", "cimd:appc:v=0:example.com/app01?version=v1.0.0", nil, }, { "example.com/app01:v1.0.0,label01=?&*/", "cimd:appc:v=0:example.com/app01?label01=%3F%26%2A%2F&version=v1.0.0", nil, }, { "some-image-name", "cimd:appc:v=0:some-image-name", nil, }, { "some-image-name:v1.0.0", "cimd:appc:v=0:some-image-name?version=v1.0.0", nil, }, { "some-image-name:f6432b725a9a5f27eaecfa47a0cbab3c0ea00f22", "cimd:appc:v=0:some-image-name?version=f6432b725a9a5f27eaecfa47a0cbab3c0ea00f22", nil, }, // ACIArchive { "file:///absolute/path/to/file.aci", "cimd:aci-archive:v=0:file%3A%2F%2F%2Fabsolute%2Fpath%2Fto%2Ffile.aci", nil, }, { "/absolute/path/to/file.aci", "cimd:aci-archive:v=0:file%3A%2F%2F%2Fabsolute%2Fpath%2Fto%2Ffile.aci", nil, }, { relPath1, "cimd:aci-archive:v=0:" + url.QueryEscape("file://"+absPath1), nil, }, { "https://example.com/app.aci", "cimd:aci-archive:v=0:https%3A%2F%2Fexample.com%2Fapp.aci", nil, }, // Path with no .aci extension { "/absolute/path/to/file", "", fmt.Errorf("invalid image string %q", "file:///absolute/path/to/file"), }, { "/absolute/path/to/file.tar", "", fmt.Errorf("invalid image string %q", "file:///absolute/path/to/file.tar"), }, { relPath2, "", fmt.Errorf("invalid image string %q", "file://"+absPath2), }, // Docker { "docker:busybox", "cimd:docker:v=0:registry-1.docker.io/library/busybox:latest", nil, }, { "docker://busybox", "cimd:docker:v=0:registry-1.docker.io/library/busybox:latest", nil, }, { "docker:busybox:latest", "cimd:docker:v=0:registry-1.docker.io/library/busybox:latest", nil, }, { "docker://*****:*****@sha256:a59906e33509d14c036c8678d687bd4eec81ed7c4b8ce907b888c607f6a1e0e6", "cimd:docker:v=0:registry-1.docker.io/library/busybox@sha256:a59906e33509d14c036c8678d687bd4eec81ed7c4b8ce907b888c607f6a1e0e6", nil, }, { "docker:myregistry.example.com:4000/busybox", "cimd:docker:v=0:myregistry.example.com:4000/busybox:latest", nil, }, { "docker:myregistry.example.com:4000/busybox", "cimd:docker:v=0:myregistry.example.com:4000/busybox:latest", nil, }, { "docker:myregistry.example.com:4000/busybox:1.0", "cimd:docker:v=0:myregistry.example.com:4000/busybox:1.0", nil, }, } for _, tt := range tests { d, err := DistFromImageString(tt.in) if err != nil { if tt.err == nil { t.Fatalf("unexpected error: %v", err) } if tt.err.Error() != err.Error() { t.Fatalf("expected error %v, but got error %v", tt.err, err) } continue } else { if tt.err != nil { t.Fatalf("expected error %v, but got nil error", tt.err) } } td, err := dist.Parse(tt.distString) if err != nil { t.Fatalf("unexpected error: %v", err) } if !d.Equals(td) { t.Fatalf("expected identical distribution but got %q != %q", tt.distString, d.CIMD().String()) } } }
// 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) }