Merge pull request #11054 from saschagrunert/login-logout-path-tests

Add `--accept-repositories` integration tests
This commit is contained in:
openshift-ci[bot]
2021-08-01 11:25:17 +00:00
committed by GitHub
17 changed files with 462 additions and 89 deletions

View File

@ -52,6 +52,7 @@ func init() {
loginOptions.Stdin = os.Stdin loginOptions.Stdin = os.Stdin
loginOptions.Stdout = os.Stdout loginOptions.Stdout = os.Stdout
loginOptions.AcceptUnspecifiedRegistry = true loginOptions.AcceptUnspecifiedRegistry = true
loginOptions.AcceptRepositories = true
} }
// Implementation of podman-login. // Implementation of podman-login.

View File

@ -43,6 +43,7 @@ func init() {
logoutOptions.Stdout = os.Stdout logoutOptions.Stdout = os.Stdout
logoutOptions.AcceptUnspecifiedRegistry = true logoutOptions.AcceptUnspecifiedRegistry = true
logoutOptions.AcceptRepositories = true
} }
// Implementation of podman-logout. // Implementation of podman-logout.

2
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/containernetworking/cni v0.8.1 github.com/containernetworking/cni v0.8.1
github.com/containernetworking/plugins v0.9.1 github.com/containernetworking/plugins v0.9.1
github.com/containers/buildah v1.21.1-0.20210721171232-54cafea4c933 github.com/containers/buildah v1.21.1-0.20210721171232-54cafea4c933
github.com/containers/common v0.41.1-0.20210721172332-291287e9d060 github.com/containers/common v0.41.1-0.20210730122913-cd6c45fd20e3
github.com/containers/conmon v2.0.20+incompatible github.com/containers/conmon v2.0.20+incompatible
github.com/containers/image/v5 v5.14.0 github.com/containers/image/v5 v5.14.0
github.com/containers/ocicrypt v1.1.2 github.com/containers/ocicrypt v1.1.2

4
go.sum
View File

@ -242,8 +242,8 @@ github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRD
github.com/containers/buildah v1.21.1-0.20210721171232-54cafea4c933 h1:jqO3hDypBoKM5be+fVcqGHOpX2fOiQy2DFEeb/VKpsk= github.com/containers/buildah v1.21.1-0.20210721171232-54cafea4c933 h1:jqO3hDypBoKM5be+fVcqGHOpX2fOiQy2DFEeb/VKpsk=
github.com/containers/buildah v1.21.1-0.20210721171232-54cafea4c933/go.mod h1:9gspFNeUJxIK72n1IMIKIHmtcePEZQsv0tjo+1LqkCo= github.com/containers/buildah v1.21.1-0.20210721171232-54cafea4c933/go.mod h1:9gspFNeUJxIK72n1IMIKIHmtcePEZQsv0tjo+1LqkCo=
github.com/containers/common v0.41.1-0.20210721112610-c95d2f794edf/go.mod h1:Ba5YVNCnyX6xDtg1JqEHa2EMVMW5UbHmIyEqsEwpeGE= github.com/containers/common v0.41.1-0.20210721112610-c95d2f794edf/go.mod h1:Ba5YVNCnyX6xDtg1JqEHa2EMVMW5UbHmIyEqsEwpeGE=
github.com/containers/common v0.41.1-0.20210721172332-291287e9d060 h1:HgGff2MeEKfYoKp2WQFl9xdsgP7KV8rr/1JZRIuPXmg= github.com/containers/common v0.41.1-0.20210730122913-cd6c45fd20e3 h1:lHOZ+G5B7aP2YPsbDo4DtALFAuFG5PWH3Pv5zL2bC08=
github.com/containers/common v0.41.1-0.20210721172332-291287e9d060/go.mod h1:Ba5YVNCnyX6xDtg1JqEHa2EMVMW5UbHmIyEqsEwpeGE= github.com/containers/common v0.41.1-0.20210730122913-cd6c45fd20e3/go.mod h1:UzAAjDsxwd4qkN1mgsk6aspduBY5bspxvKgwQElaBwk=
github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg= github.com/containers/conmon v2.0.20+incompatible h1:YbCVSFSCqFjjVwHTPINGdMX1F6JXHGTUje2ZYobNrkg=
github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I= github.com/containers/conmon v2.0.20+incompatible/go.mod h1:hgwZ2mtuDrppv78a/cOBNiCm6O0UMWGx1mu7P00nu5I=
github.com/containers/image/v5 v5.13.2/go.mod h1:GkWursKDlDcUIT7L7vZf70tADvZCk/Ga0wgS0MuF0ag= github.com/containers/image/v5 v5.13.2/go.mod h1:GkWursKDlDcUIT7L7vZf70tADvZCk/Ga0wgS0MuF0ag=

View File

@ -97,6 +97,24 @@ var _ = Describe("Podman login and logout", func() {
os.RemoveAll(certDirPath) os.RemoveAll(certDirPath)
}) })
readAuthInfo := func(filePath string) map[string]interface{} {
authBytes, err := ioutil.ReadFile(filePath)
Expect(err).To(BeNil())
var authInfo map[string]interface{}
err = json.Unmarshal(authBytes, &authInfo)
Expect(err).To(BeNil())
fmt.Println(authInfo)
const authsKey = "auths"
Expect(authInfo).To(HaveKey(authsKey))
auths, ok := authInfo[authsKey].(map[string]interface{})
Expect(ok).To(BeTrue())
return auths
}
It("podman login and logout", func() { It("podman login and logout", func() {
session := podmanTest.Podman([]string{"login", "-u", "podmantest", "-p", "test", server}) session := podmanTest.Podman([]string{"login", "-u", "podmantest", "-p", "test", server})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
@ -151,10 +169,7 @@ var _ = Describe("Podman login and logout", func() {
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0)) Expect(session).Should(Exit(0))
authInfo, _ := ioutil.ReadFile(authFile) readAuthInfo(authFile)
var info map[string]interface{}
json.Unmarshal(authInfo, &info)
fmt.Println(info)
// push should fail with nonexistent authfile // push should fail with nonexistent authfile
session = podmanTest.Podman([]string{"push", "--authfile", "/tmp/nonexistent", ALPINE, testImg}) session = podmanTest.Podman([]string{"push", "--authfile", "/tmp/nonexistent", ALPINE, testImg})
@ -284,4 +299,204 @@ var _ = Describe("Podman login and logout", func() {
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError()) Expect(session).To(ExitWithError())
}) })
It("podman login and logout with repository", func() {
authFile := filepath.Join(podmanTest.TempDir, "auth.json")
testRepository := server + "/podmantest"
session := podmanTest.Podman([]string{
"login",
"-u", "podmantest",
"-p", "test",
"--authfile", authFile,
testRepository,
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
authInfo := readAuthInfo(authFile)
Expect(authInfo).To(HaveKey(testRepository))
session = podmanTest.Podman([]string{
"logout",
"--authfile", authFile,
testRepository,
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
authInfo = readAuthInfo(authFile)
Expect(authInfo).NotTo(HaveKey(testRepository))
})
It("podman login and logout with repository and specified image", func() {
authFile := filepath.Join(podmanTest.TempDir, "auth.json")
testTarget := server + "/podmantest/test-alpine"
session := podmanTest.Podman([]string{
"login",
"-u", "podmantest",
"-p", "test",
"--authfile", authFile,
testTarget,
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
authInfo := readAuthInfo(authFile)
Expect(authInfo).To(HaveKey(testTarget))
session = podmanTest.Podman([]string{
"push",
"--authfile", authFile,
ALPINE, testTarget,
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
})
It("podman login and logout with repository with fallback", func() {
authFile := filepath.Join(podmanTest.TempDir, "auth.json")
testRepos := []string{
server + "/podmantest",
server,
}
for _, testRepo := range testRepos {
session := podmanTest.Podman([]string{
"login",
"-u", "podmantest",
"-p", "test",
"--authfile", authFile,
testRepo,
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
}
authInfo := readAuthInfo(authFile)
Expect(authInfo).To(HaveKey(testRepos[0]))
Expect(authInfo).To(HaveKey(testRepos[1]))
session := podmanTest.Podman([]string{
"push",
"--authfile", authFile,
ALPINE, testRepos[0] + "/test-image-alpine",
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{
"logout",
"--authfile", authFile,
testRepos[0],
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{
"push",
"--authfile", authFile,
ALPINE, testRepos[0] + "/test-image-alpine",
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{
"logout",
"--authfile", authFile,
testRepos[1],
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
authInfo = readAuthInfo(authFile)
Expect(authInfo).NotTo(HaveKey(testRepos[0]))
Expect(authInfo).NotTo(HaveKey(testRepos[1]))
})
It("podman login with repository invalid arguments", func() {
authFile := filepath.Join(podmanTest.TempDir, "auth.json")
for _, invalidArg := range []string{
"https://" + server + "/podmantest",
server + "/podmantest/image:latest",
} {
session := podmanTest.Podman([]string{
"login",
"-u", "podmantest",
"-p", "test",
"--authfile", authFile,
invalidArg,
})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitWithError())
}
})
It("podman login and logout with repository push with invalid auth.json credentials", func() {
authFile := filepath.Join(podmanTest.TempDir, "auth.json")
// only `server` contains the correct login data
err := ioutil.WriteFile(authFile, []byte(fmt.Sprintf(`{"auths": {
"%s/podmantest": { "auth": "cG9kbWFudGVzdDp3cm9uZw==" },
"%s": { "auth": "cG9kbWFudGVzdDp0ZXN0" }
}}`, server, server)), 0644)
Expect(err).To(BeNil())
session := podmanTest.Podman([]string{
"push",
"--authfile", authFile,
ALPINE, server + "/podmantest/test-image",
})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError())
session = podmanTest.Podman([]string{
"push",
"--authfile", authFile,
ALPINE, server + "/test-image",
})
session.WaitWithDefaultTimeout()
Expect(session).To(Exit(0))
})
It("podman login and logout with repository pull with wrong auth.json credentials", func() {
authFile := filepath.Join(podmanTest.TempDir, "auth.json")
testTarget := server + "/podmantest/test-alpine"
session := podmanTest.Podman([]string{
"login",
"-u", "podmantest",
"-p", "test",
"--authfile", authFile,
testTarget,
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
session = podmanTest.Podman([]string{
"push",
"--authfile", authFile,
ALPINE, testTarget,
})
session.WaitWithDefaultTimeout()
Expect(session).Should(Exit(0))
// only `server + /podmantest` and `server` have the correct login data
err := ioutil.WriteFile(authFile, []byte(fmt.Sprintf(`{"auths": {
"%s/podmantest/test-alpine": { "auth": "cG9kbWFudGVzdDp3cm9uZw==" },
"%s/podmantest": { "auth": "cG9kbWFudGVzdDp0ZXN0" },
"%s": { "auth": "cG9kbWFudGVzdDp0ZXN0" }
}}`, server, server, server)), 0644)
Expect(err).To(BeNil())
session = podmanTest.Podman([]string{
"pull",
"--authfile", authFile,
server + "/podmantest/test-alpine",
})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitWithError())
})
}) })

View File

@ -342,7 +342,7 @@ func (c *copier) copy(ctx context.Context, source, destination types.ImageRefere
} }
} }
var copiedManifest []byte var returnManifest []byte
f := func() error { f := func() error {
opts := c.imageCopyOptions opts := c.imageCopyOptions
if sourceInsecure != nil { if sourceInsecure != nil {
@ -354,11 +354,13 @@ func (c *copier) copy(ctx context.Context, source, destination types.ImageRefere
opts.DestinationCtx.DockerInsecureSkipTLSVerify = value opts.DestinationCtx.DockerInsecureSkipTLSVerify = value
} }
var err error copiedManifest, err := copy.Image(ctx, c.policyContext, destination, source, &opts)
copiedManifest, err = copy.Image(ctx, c.policyContext, destination, source, &opts) if err == nil {
returnManifest = copiedManifest
}
return err return err
} }
return copiedManifest, retry.RetryIfNecessary(ctx, f, &c.retryOptions) return returnManifest, retry.RetryIfNecessary(ctx, f, &c.retryOptions)
} }
// checkRegistrySourcesAllows checks the $BUILD_REGISTRY_SOURCES environment // checkRegistrySourcesAllows checks the $BUILD_REGISTRY_SOURCES environment
@ -369,7 +371,7 @@ func (c *copier) copy(ctx context.Context, source, destination types.ImageRefere
// If set, the insecure return value indicates whether the registry is set to // If set, the insecure return value indicates whether the registry is set to
// be insecure. // be insecure.
// //
// NOTE: this functionality is required by Buildah. // NOTE: this functionality is required by Buildah for OpenShift.
func checkRegistrySourcesAllows(dest types.ImageReference) (insecure *bool, err error) { func checkRegistrySourcesAllows(dest types.ImageReference) (insecure *bool, err error) {
registrySources, ok := os.LookupEnv("BUILD_REGISTRY_SOURCES") registrySources, ok := os.LookupEnv("BUILD_REGISTRY_SOURCES")
if !ok || registrySources == "" { if !ok || registrySources == "" {

View File

@ -836,9 +836,9 @@ func (i *Image) Manifest(ctx context.Context) (rawManifest []byte, mimeType stri
return src.GetManifest(ctx, nil) return src.GetManifest(ctx, nil)
} }
// getImageDigest creates an image object and uses the hex value of the digest as the image ID // getImageID creates an image object and uses the hex value of the config
// for parsing the store reference // blob's digest (if it has one) as the image ID for parsing the store reference
func getImageDigest(ctx context.Context, src types.ImageReference, sys *types.SystemContext) (string, error) { func getImageID(ctx context.Context, src types.ImageReference, sys *types.SystemContext) (string, error) {
newImg, err := src.NewImage(ctx, sys) newImg, err := src.NewImage(ctx, sys)
if err != nil { if err != nil {
return "", err return "", err
@ -852,5 +852,5 @@ func getImageDigest(ctx context.Context, src types.ImageReference, sys *types.Sy
if err = imageDigest.Validate(); err != nil { if err = imageDigest.Validate(); err != nil {
return "", errors.Wrapf(err, "error getting config info") return "", errors.Wrapf(err, "error getting config info")
} }
return "@" + imageDigest.Hex(), nil return "@" + imageDigest.Encoded(), nil
} }

View File

@ -86,7 +86,7 @@ func (r *Runtime) Import(ctx context.Context, path string, options *ImportOption
return "", err return "", err
} }
id, err := getImageDigest(ctx, srcRef, r.systemContextCopy()) id, err := getImageID(ctx, srcRef, r.systemContextCopy())
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types" "github.com/containers/image/v5/types"
"github.com/containers/storage" "github.com/containers/storage"
"github.com/containers/storage/pkg/lockfile"
digest "github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
v1 "github.com/opencontainers/image-spec/specs-go/v1" v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -395,3 +396,20 @@ func (l *list) Remove(instanceDigest digest.Digest) error {
} }
return err return err
} }
// LockerForImage returns a Locker for a given image record. It's recommended
// that processes which use LoadFromImage() to load a list from an image and
// then use that list's SaveToImage() method to save a modified version of the
// list to that image record use this lock to avoid accidentally wiping out
// changes that another process is also attempting to make.
func LockerForImage(store storage.Store, image string) (lockfile.Locker, error) {
img, err := store.Image(image)
if err != nil {
return nil, errors.Wrapf(err, "locating image %q for locating lock", image)
}
d := digest.NewDigestFromEncoded(digest.Canonical, img.ID)
if err := d.Validate(); err != nil {
return nil, errors.Wrapf(err, "coercing image ID for %q into a digest", image)
}
return store.GetDigestLock(d)
}

View File

@ -12,6 +12,7 @@ import (
dockerArchiveTransport "github.com/containers/image/v5/docker/archive" dockerArchiveTransport "github.com/containers/image/v5/docker/archive"
dockerDaemonTransport "github.com/containers/image/v5/docker/daemon" dockerDaemonTransport "github.com/containers/image/v5/docker/daemon"
"github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
ociArchiveTransport "github.com/containers/image/v5/oci/archive" ociArchiveTransport "github.com/containers/image/v5/oci/archive"
ociTransport "github.com/containers/image/v5/oci/layout" ociTransport "github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/pkg/shortnames" "github.com/containers/image/v5/pkg/shortnames"
@ -19,6 +20,7 @@ import (
"github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types" "github.com/containers/image/v5/types"
"github.com/containers/storage" "github.com/containers/storage"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -192,19 +194,19 @@ func (r *Runtime) copyFromDefault(ctx context.Context, ref types.ImageReference,
imageName = storageName imageName = storageName
case ociArchiveTransport.Transport.Name(): case ociArchiveTransport.Transport.Name():
manifest, err := ociArchiveTransport.LoadManifestDescriptor(ref) manifestDescriptor, err := ociArchiveTransport.LoadManifestDescriptor(ref)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// if index.json has no reference name, compute the image digest instead // if index.json has no reference name, compute the image ID instead
if manifest.Annotations == nil || manifest.Annotations["org.opencontainers.image.ref.name"] == "" { if manifestDescriptor.Annotations == nil || manifestDescriptor.Annotations["org.opencontainers.image.ref.name"] == "" {
storageName, err = getImageDigest(ctx, ref, nil) storageName, err = getImageID(ctx, ref, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
imageName = "sha256:" + storageName[1:] imageName = "sha256:" + storageName[1:]
} else { } else {
storageName = manifest.Annotations["org.opencontainers.image.ref.name"] storageName = manifestDescriptor.Annotations["org.opencontainers.image.ref.name"]
named, err := NormalizeName(storageName) named, err := NormalizeName(storageName)
if err != nil { if err != nil {
return nil, err return nil, err
@ -248,7 +250,7 @@ func (r *Runtime) storageReferencesReferencesFromArchiveReader(ctx context.Conte
var imageNames []string var imageNames []string
if len(destNames) == 0 { if len(destNames) == 0 {
destName, err := getImageDigest(ctx, readerRef, &r.systemContext) destName, err := getImageID(ctx, readerRef, &r.systemContext)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -316,8 +318,8 @@ func (r *Runtime) copyFromDockerArchiveReaderReference(ctx context.Context, read
} }
// copyFromRegistry pulls the specified, possibly unqualified, name from a // copyFromRegistry pulls the specified, possibly unqualified, name from a
// registry. On successful pull it returns the used fully-qualified name that // registry. On successful pull it returns the ID of the image in local
// can later be used to look up the image in the local containers storage. // storage.
// //
// If options.All is set, all tags from the specified registry will be pulled. // If options.All is set, all tags from the specified registry will be pulled.
func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference, inputName string, pullPolicy config.PullPolicy, options *PullOptions) ([]string, error) { func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference, inputName string, pullPolicy config.PullPolicy, options *PullOptions) ([]string, error) {
@ -337,7 +339,7 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference
return nil, err return nil, err
} }
pulledTags := []string{} pulledIDs := []string{}
for _, tag := range tags { for _, tag := range tags {
select { // Let's be gentle with Podman remote. select { // Let's be gentle with Podman remote.
case <-ctx.Done(): case <-ctx.Done():
@ -353,15 +355,54 @@ func (r *Runtime) copyFromRegistry(ctx context.Context, ref types.ImageReference
if err != nil { if err != nil {
return nil, err return nil, err
} }
pulledTags = append(pulledTags, pulled...) pulledIDs = append(pulledIDs, pulled...)
} }
return pulledTags, nil return pulledIDs, nil
}
// imageIDsForManifest() parses the manifest of the copied image and then looks
// up the IDs of the matching image. There's a small slice of time, between
// when we copy the image into local storage and when we go to look for it
// using the name that we gave it when we copied it, when the name we wanted to
// assign to the image could have been moved, but the image's ID will remain
// the same until it is deleted.
func (r *Runtime) imagesIDsForManifest(manifestBytes []byte, sys *types.SystemContext) ([]string, error) {
var imageDigest digest.Digest
manifestType := manifest.GuessMIMEType(manifestBytes)
if manifest.MIMETypeIsMultiImage(manifestType) {
list, err := manifest.ListFromBlob(manifestBytes, manifestType)
if err != nil {
return nil, errors.Wrapf(err, "parsing manifest list")
}
d, err := list.ChooseInstance(sys)
if err != nil {
return nil, errors.Wrapf(err, "choosing instance from manifest list")
}
imageDigest = d
} else {
d, err := manifest.Digest(manifestBytes)
if err != nil {
return nil, errors.Wrapf(err, "digesting manifest")
}
imageDigest = d
}
var results []string
images, err := r.store.ImagesByDigest(imageDigest)
if err != nil {
return nil, errors.Wrapf(err, "listing images by manifest digest")
}
for _, image := range images {
results = append(results, image.ID)
}
if len(results) == 0 {
return nil, errors.Wrapf(storage.ErrImageUnknown, "identifying new image by manifest digest")
}
return results, nil
} }
// copySingleImageFromRegistry pulls the specified, possibly unqualified, name // copySingleImageFromRegistry pulls the specified, possibly unqualified, name
// from a registry. On successful pull it returns the used fully-qualified // from a registry. On successful pull it returns the ID of the image in local
// name that can later be used to look up the image in the local containers
// storage. // storage.
func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy config.PullPolicy, options *PullOptions) ([]string, error) { //nolint:gocyclo func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName string, pullPolicy config.PullPolicy, options *PullOptions) ([]string, error) { //nolint:gocyclo
// Sanity check. // Sanity check.
@ -375,7 +416,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
err error err error
) )
// Always check if there's a local image. If, we should use it's // Always check if there's a local image. If so, we should use its
// resolved name for pulling. Assume we're doing a `pull foo`. // resolved name for pulling. Assume we're doing a `pull foo`.
// If there's already a local image "localhost/foo", then we should // If there's already a local image "localhost/foo", then we should
// attempt pulling that instead of doing the full short-name dance. // attempt pulling that instead of doing the full short-name dance.
@ -454,7 +495,7 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
} }
} }
// If we found a local image, we should use it's locally resolved name // If we found a local image, we should use its locally resolved name
// (see containers/buildah/issues/2904). An exception is if a custom // (see containers/buildah/issues/2904). An exception is if a custom
// platform is specified (e.g., `--arch=arm64`). In that case, we need // platform is specified (e.g., `--arch=arm64`). In that case, we need
// to pessimistically pull the image since some images declare wrong // to pessimistically pull the image since some images declare wrong
@ -462,7 +503,8 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
// containers/podman/issues/10682). // containers/podman/issues/10682).
// //
// In other words: multi-arch support can only be as good as the images // In other words: multi-arch support can only be as good as the images
// in the wild. // in the wild, so we shouldn't break things for our users by trying to
// insist that they make sense.
if localImage != nil && !customPlatform { if localImage != nil && !customPlatform {
if imageName != resolvedImageName { if imageName != resolvedImageName {
logrus.Debugf("Image %s resolved to local image %s which will be used for pulling", imageName, resolvedImageName) logrus.Debugf("Image %s resolved to local image %s which will be used for pulling", imageName, resolvedImageName)
@ -541,7 +583,8 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
return nil, err return nil, err
} }
} }
if _, err := c.copy(ctx, srcRef, destRef); err != nil { var manifestBytes []byte
if manifestBytes, err = c.copy(ctx, srcRef, destRef); err != nil {
logrus.Debugf("Error pulling candidate %s: %v", candidateString, err) logrus.Debugf("Error pulling candidate %s: %v", candidateString, err)
pullErrors = append(pullErrors, err) pullErrors = append(pullErrors, err)
continue continue
@ -554,6 +597,9 @@ func (r *Runtime) copySingleImageFromRegistry(ctx context.Context, imageName str
} }
logrus.Debugf("Pulled candidate %s successfully", candidateString) logrus.Debugf("Pulled candidate %s successfully", candidateString)
if ids, err := r.imagesIDsForManifest(manifestBytes, sys); err == nil {
return ids, nil
}
return []string{candidate.Value.String()}, nil return []string{candidate.Value.String()}, nil
} }

View File

@ -185,6 +185,10 @@ func (r *Runtime) searchImageInRegistry(ctx context.Context, term, registry stri
sys.DockerInsecureSkipTLSVerify = options.InsecureSkipTLSVerify sys.DockerInsecureSkipTLSVerify = options.InsecureSkipTLSVerify
} }
if options.Authfile != "" {
sys.AuthFilePath = options.Authfile
}
if options.ListTags { if options.ListTags {
results, err := searchRepositoryTags(ctx, sys, registry, term, options) results, err := searchRepositoryTags(ctx, sys, registry, term, options)
if err != nil { if err != nil {

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"github.com/containers/image/v5/docker" "github.com/containers/image/v5/docker"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/pkg/docker/config" "github.com/containers/image/v5/pkg/docker/config"
"github.com/containers/image/v5/pkg/sysregistriesv2" "github.com/containers/image/v5/pkg/sysregistriesv2"
"github.com/containers/image/v5/types" "github.com/containers/image/v5/types"
@ -69,30 +70,50 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO
systemContext = systemContextWithOptions(systemContext, opts.AuthFile, opts.CertDir) systemContext = systemContextWithOptions(systemContext, opts.AuthFile, opts.CertDir)
var ( var (
server string authConfig types.DockerAuthConfig
err error key, registry string
ref reference.Named
err error
) )
if len(args) > 1 { l := len(args)
return errors.New("login accepts only one registry to login to") switch l {
} case 0:
if len(args) == 0 {
if !opts.AcceptUnspecifiedRegistry { if !opts.AcceptUnspecifiedRegistry {
return errors.New("please provide a registry to login to") return errors.New("please provide a registry to login to")
} }
if server, err = defaultRegistryWhenUnspecified(systemContext); err != nil { if key, err = defaultRegistryWhenUnspecified(systemContext); err != nil {
return err return err
} }
logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", server) registry = key
logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", key)
case 1:
key, registry, ref, err = parseRegistryArgument(args[0], opts.AcceptRepositories)
if err != nil {
return err
}
default:
return errors.New("login accepts only one registry to login to")
}
if ref != nil {
authConfig, err = config.GetCredentialsForRef(systemContext, ref)
if err != nil {
return errors.Wrap(err, "get credentials for repository")
}
} else { } else {
server = getRegistryName(args[0]) // nolint: staticcheck
} authConfig, err = config.GetCredentials(systemContext, registry)
authConfig, err := config.GetCredentials(systemContext, server) if err != nil {
if err != nil { return errors.Wrap(err, "get credentials")
return errors.Wrap(err, "reading auth file") }
} }
if opts.GetLoginSet { if opts.GetLoginSet {
if authConfig.Username == "" { if authConfig.Username == "" {
return errors.Errorf("not logged into %s", server) return errors.Errorf("not logged into %s", key)
} }
fmt.Fprintf(opts.Stdout, "%s\n", authConfig.Username) fmt.Fprintf(opts.Stdout, "%s\n", authConfig.Username)
return nil return nil
@ -119,9 +140,9 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO
// If no username and no password is specified, try to use existing ones. // If no username and no password is specified, try to use existing ones.
if opts.Username == "" && password == "" && authConfig.Username != "" && authConfig.Password != "" { if opts.Username == "" && password == "" && authConfig.Username != "" && authConfig.Password != "" {
fmt.Println("Authenticating with existing credentials...") fmt.Fprintf(opts.Stdout, "Authenticating with existing credentials for %s\n", key)
if err := docker.CheckAuth(ctx, systemContext, authConfig.Username, authConfig.Password, server); err == nil { if err := docker.CheckAuth(ctx, systemContext, authConfig.Username, authConfig.Password, registry); err == nil {
fmt.Fprintln(opts.Stdout, "Existing credentials are valid. Already logged in to", server) fmt.Fprintf(opts.Stdout, "Existing credentials are valid. Already logged in to %s\n", registry)
return nil return nil
} }
fmt.Fprintln(opts.Stdout, "Existing credentials are invalid, please enter valid username and password") fmt.Fprintln(opts.Stdout, "Existing credentials are invalid, please enter valid username and password")
@ -132,9 +153,9 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO
return errors.Wrap(err, "getting username and password") return errors.Wrap(err, "getting username and password")
} }
if err = docker.CheckAuth(ctx, systemContext, username, password, server); err == nil { if err = docker.CheckAuth(ctx, systemContext, username, password, registry); err == nil {
// Write the new credentials to the authfile // Write the new credentials to the authfile
desc, err := config.SetCredentials(systemContext, server, username, password) desc, err := config.SetCredentials(systemContext, key, username, password)
if err != nil { if err != nil {
return err return err
} }
@ -147,10 +168,45 @@ func Login(ctx context.Context, systemContext *types.SystemContext, opts *LoginO
return nil return nil
} }
if unauthorized, ok := err.(docker.ErrUnauthorizedForCredentials); ok { if unauthorized, ok := err.(docker.ErrUnauthorizedForCredentials); ok {
logrus.Debugf("error logging into %q: %v", server, unauthorized) logrus.Debugf("error logging into %q: %v", key, unauthorized)
return errors.Errorf("error logging into %q: invalid username/password", server) return errors.Errorf("error logging into %q: invalid username/password", key)
} }
return errors.Wrapf(err, "authenticating creds for %q", server) return errors.Wrapf(err, "authenticating creds for %q", key)
}
// parseRegistryArgument verifies the provided arg depending if we accept
// repositories or not.
func parseRegistryArgument(arg string, acceptRepositories bool) (key, registry string, maybeRef reference.Named, err error) {
if !acceptRepositories {
registry = getRegistryName(arg)
key = registry
return key, registry, maybeRef, nil
}
key = trimScheme(arg)
if key != arg {
return key, registry, nil, errors.New("credentials key has https[s]:// prefix")
}
registry = getRegistryName(key)
if registry == key {
// We cannot parse a reference from a registry, so we stop here
return key, registry, nil, nil
}
ref, parseErr := reference.ParseNamed(key)
if parseErr != nil {
return key, registry, nil, errors.Wrapf(parseErr, "parse reference from %q", key)
}
if !reference.IsNameOnly(ref) {
return key, registry, nil, errors.Errorf("reference %q contains tag or digest", ref.String())
}
maybeRef = ref
registry = reference.Domain(ref)
return key, registry, maybeRef, nil
} }
// getRegistryName scrubs and parses the input to get the server name // getRegistryName scrubs and parses the input to get the server name
@ -158,13 +214,21 @@ func getRegistryName(server string) string {
// removes 'http://' or 'https://' from the front of the // removes 'http://' or 'https://' from the front of the
// server/registry string if either is there. This will be mostly used // server/registry string if either is there. This will be mostly used
// for user input from 'Buildah login' and 'Buildah logout'. // for user input from 'Buildah login' and 'Buildah logout'.
server = strings.TrimPrefix(strings.TrimPrefix(server, "https://"), "http://") server = trimScheme(server)
// gets the registry from the input. If the input is of the form // gets the registry from the input. If the input is of the form
// quay.io/myuser/myimage, it will parse it and just return quay.io // quay.io/myuser/myimage, it will parse it and just return quay.io
split := strings.Split(server, "/") split := strings.Split(server, "/")
return split[0] return split[0]
} }
// trimScheme removes the HTTP(s) scheme from the provided repository.
func trimScheme(repository string) string {
// removes 'http://' or 'https://' from the front of the
// server/registry string if either is there. This will be mostly used
// for user input from 'Buildah login' and 'Buildah logout'.
return strings.TrimPrefix(strings.TrimPrefix(repository, "https://"), "http://")
}
// getUserAndPass gets the username and password from STDIN if not given // getUserAndPass gets the username and password from STDIN if not given
// using the -u and -p flags. If the username prompt is left empty, the // using the -u and -p flags. If the username prompt is left empty, the
// displayed userFromAuthFile will be used instead. // displayed userFromAuthFile will be used instead.
@ -209,8 +273,9 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri
systemContext = systemContextWithOptions(systemContext, opts.AuthFile, "") systemContext = systemContextWithOptions(systemContext, opts.AuthFile, "")
var ( var (
server string key, registry string
err error ref reference.Named
err error
) )
if len(args) > 1 { if len(args) > 1 {
return errors.New("logout accepts only one registry to logout from") return errors.New("logout accepts only one registry to logout from")
@ -219,16 +284,20 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri
if !opts.AcceptUnspecifiedRegistry { if !opts.AcceptUnspecifiedRegistry {
return errors.New("please provide a registry to logout from") return errors.New("please provide a registry to logout from")
} }
if server, err = defaultRegistryWhenUnspecified(systemContext); err != nil { if key, err = defaultRegistryWhenUnspecified(systemContext); err != nil {
return err return err
} }
logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", server) registry = key
logrus.Debugf("registry not specified, default to the first registry %q from registries.conf", key)
} }
if len(args) != 0 { if len(args) != 0 {
if opts.All { if opts.All {
return errors.New("--all takes no arguments") return errors.New("--all takes no arguments")
} }
server = getRegistryName(args[0]) key, registry, ref, err = parseRegistryArgument(args[0], opts.AcceptRepositories)
if err != nil {
return err
}
} }
if opts.All { if opts.All {
@ -239,24 +308,34 @@ func Logout(systemContext *types.SystemContext, opts *LogoutOptions, args []stri
return nil return nil
} }
err = config.RemoveAuthentication(systemContext, server) err = config.RemoveAuthentication(systemContext, key)
switch errors.Cause(err) { switch errors.Cause(err) {
case nil: case nil:
fmt.Fprintf(opts.Stdout, "Removed login credentials for %s\n", server) fmt.Fprintf(opts.Stdout, "Removed login credentials for %s\n", key)
return nil return nil
case config.ErrNotLoggedIn: case config.ErrNotLoggedIn:
authConfig, err := config.GetCredentials(systemContext, server) var authConfig types.DockerAuthConfig
if err != nil { if ref != nil {
return errors.Wrap(err, "reading auth file") authConfig, err = config.GetCredentialsForRef(systemContext, ref)
if err != nil {
return errors.Wrap(err, "get credentials for repository")
}
} else {
// nolint: staticcheck
authConfig, err = config.GetCredentials(systemContext, registry)
if err != nil {
return errors.Wrap(err, "get credentials")
}
} }
authInvalid := docker.CheckAuth(context.Background(), systemContext, authConfig.Username, authConfig.Password, server)
authInvalid := docker.CheckAuth(context.Background(), systemContext, authConfig.Username, authConfig.Password, registry)
if authConfig.Username != "" && authConfig.Password != "" && authInvalid == nil { if authConfig.Username != "" && authConfig.Password != "" && authInvalid == nil {
fmt.Printf("Not logged into %s with current tool. Existing credentials were established via docker login. Please use docker logout instead.\n", server) fmt.Printf("Not logged into %s with current tool. Existing credentials were established via docker login. Please use docker logout instead.\n", key)
return nil return nil
} }
return errors.Errorf("Not logged into %s\n", server) return errors.Errorf("Not logged into %s\n", key)
default: default:
return errors.Wrapf(err, "logging out of %q", server) return errors.Wrapf(err, "logging out of %q", key)
} }
} }

View File

@ -14,13 +14,14 @@ type LoginOptions struct {
// CLI flags managed by the FlagSet returned by GetLoginFlags // CLI flags managed by the FlagSet returned by GetLoginFlags
// Callers that use GetLoginFlags should not need to touch these values at all; callers that use // Callers that use GetLoginFlags should not need to touch these values at all; callers that use
// other CLI frameworks should set them based on user input. // other CLI frameworks should set them based on user input.
AuthFile string AuthFile string
CertDir string CertDir string
Password string Password string
Username string Username string
StdinPassword bool StdinPassword bool
GetLoginSet bool GetLoginSet bool
Verbose bool // set to true for verbose output Verbose bool // set to true for verbose output
AcceptRepositories bool // set to true to allow namespaces or repositories rather than just registries
// Options caller can set // Options caller can set
Stdin io.Reader // set to os.Stdin Stdin io.Reader // set to os.Stdin
Stdout io.Writer // set to os.Stdout Stdout io.Writer // set to os.Stdout
@ -32,8 +33,9 @@ type LogoutOptions struct {
// CLI flags managed by the FlagSet returned by GetLogoutFlags // CLI flags managed by the FlagSet returned by GetLogoutFlags
// Callers that use GetLogoutFlags should not need to touch these values at all; callers that use // Callers that use GetLogoutFlags should not need to touch these values at all; callers that use
// other CLI frameworks should set them based on user input. // other CLI frameworks should set them based on user input.
AuthFile string AuthFile string
All bool All bool
AcceptRepositories bool // set to true to allow namespaces or repositories rather than just registries
// Options caller can set // Options caller can set
Stdout io.Writer // set to os.Stdout Stdout io.Writer // set to os.Stdout
AcceptUnspecifiedRegistry bool // set to true if allows logout with unspecified registry AcceptUnspecifiedRegistry bool // set to true if allows logout with unspecified registry

View File

@ -637,9 +637,14 @@ func (c *Config) CheckCgroupsAndAdjustConfig() {
session := os.Getenv("DBUS_SESSION_BUS_ADDRESS") session := os.Getenv("DBUS_SESSION_BUS_ADDRESS")
hasSession := session != "" hasSession := session != ""
if hasSession && strings.HasPrefix(session, "unix:path=") { if hasSession {
_, err := os.Stat(strings.TrimPrefix(session, "unix:path=")) for _, part := range strings.Split(session, ",") {
hasSession = err == nil if strings.HasPrefix(part, "unix:path=") {
_, err := os.Stat(strings.TrimPrefix(part, "unix:path="))
hasSession = err == nil
break
}
}
} }
if !hasSession && unshare.GetRootlessUID() != 0 { if !hasSession && unshare.GetRootlessUID() != 0 {

View File

@ -76,13 +76,13 @@ func (p PullPolicy) Validate() error {
// * "never" <-> PullPolicyNever // * "never" <-> PullPolicyNever
func ParsePullPolicy(s string) (PullPolicy, error) { func ParsePullPolicy(s string) (PullPolicy, error) {
switch s { switch s {
case "always": case "always", "Always":
return PullPolicyAlways, nil return PullPolicyAlways, nil
case "missing", "ifnotpresent", "": case "missing", "Missing", "ifnotpresent", "IfNotPresent", "":
return PullPolicyMissing, nil return PullPolicyMissing, nil
case "newer", "ifnewer": case "newer", "Newer", "ifnewer", "IfNewer":
return PullPolicyNewer, nil return PullPolicyNewer, nil
case "never": case "never", "Never":
return PullPolicyNever, nil return PullPolicyNever, nil
default: default:
return PullPolicyUnsupported, errors.Errorf("unsupported pull policy %q", s) return PullPolicyUnsupported, errors.Errorf("unsupported pull policy %q", s)

View File

@ -7,7 +7,7 @@ package seccomp
// Seccomp represents the config for a seccomp profile for syscall restriction. // Seccomp represents the config for a seccomp profile for syscall restriction.
type Seccomp struct { type Seccomp struct {
DefaultAction Action `json:"defaultAction"` DefaultAction Action `json:"defaultAction"`
DefaultErrnoRet *uint `json:"defaultErrnoRet"` DefaultErrnoRet *uint `json:"defaultErrnoRet,omitempty"`
// Architectures is kept to maintain backward compatibility with the old // Architectures is kept to maintain backward compatibility with the old
// seccomp profile. // seccomp profile.
Architectures []Arch `json:"architectures,omitempty"` Architectures []Arch `json:"architectures,omitempty"`

2
vendor/modules.txt vendored
View File

@ -93,7 +93,7 @@ github.com/containers/buildah/pkg/overlay
github.com/containers/buildah/pkg/parse github.com/containers/buildah/pkg/parse
github.com/containers/buildah/pkg/rusage github.com/containers/buildah/pkg/rusage
github.com/containers/buildah/util github.com/containers/buildah/util
# github.com/containers/common v0.41.1-0.20210721172332-291287e9d060 # github.com/containers/common v0.41.1-0.20210730122913-cd6c45fd20e3
github.com/containers/common/libimage github.com/containers/common/libimage
github.com/containers/common/libimage/manifests github.com/containers/common/libimage/manifests
github.com/containers/common/pkg/apparmor github.com/containers/common/pkg/apparmor