From ba788c1bb8ab0be85c946b698630f639f315354c Mon Sep 17 00:00:00 2001 From: Urvashi Mohnani Date: Thu, 28 Sep 2023 12:11:12 -0400 Subject: [PATCH] Fix broken podman images filters The id, digest, and intermediate filters were broken for podman images. Fix to match on substrings instead of the whole string for id and digest. Add the intermediate value correctly when set. Signed-off-by: Urvashi Mohnani --- go.mod | 4 +- go.sum | 8 ++-- pkg/domain/infra/abi/images_list.go | 3 +- test/e2e/images_test.go | 38 +++++++++++++++++ .../containers/common/libimage/filters.go | 10 ++--- .../containers/common/libimage/image.go | 11 +++++ .../common/libimage/manifest_list.go | 2 + .../common/libimage/manifests/manifests.go | 42 +++++++++++++++---- .../common/libnetwork/netavark/config.go | 5 ++- .../common/libnetwork/types/const.go | 1 + .../common/libnetwork/util/filters.go | 2 +- .../common/pkg/subscriptions/subscriptions.go | 29 +++++++++++++ vendor/github.com/onsi/gomega/CHANGELOG.md | 15 +++++++ vendor/github.com/onsi/gomega/gomega_dsl.go | 2 +- vendor/github.com/onsi/gomega/matchers.go | 2 +- .../gomega/matchers/have_http_body_matcher.go | 9 ++-- vendor/modules.txt | 4 +- 17 files changed, 157 insertions(+), 30 deletions(-) diff --git a/go.mod b/go.mod index b6181bba27..e455929620 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/containernetworking/cni v1.1.2 github.com/containernetworking/plugins v1.3.0 github.com/containers/buildah v1.32.0 - github.com/containers/common v0.56.1-0.20230927174306-9342cdd82aa6 + github.com/containers/common v0.56.1-0.20230929120646-4fd26cf84f19 github.com/containers/conmon v2.0.20+incompatible github.com/containers/gvisor-tap-vsock v0.7.1 github.com/containers/image/v5 v5.28.0 @@ -45,7 +45,7 @@ require ( github.com/moby/term v0.5.0 github.com/nxadm/tail v1.4.8 github.com/onsi/ginkgo/v2 v2.12.1 - github.com/onsi/gomega v1.27.10 + github.com/onsi/gomega v1.28.0 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.0-rc5 github.com/opencontainers/runc v1.1.9 diff --git a/go.sum b/go.sum index 64945706e6..3cef473b86 100644 --- a/go.sum +++ b/go.sum @@ -249,8 +249,8 @@ github.com/containernetworking/plugins v1.3.0 h1:QVNXMT6XloyMUoO2wUOqWTC1hWFV62Q github.com/containernetworking/plugins v1.3.0/go.mod h1:Pc2wcedTQQCVuROOOaLBPPxrEXqqXBFt3cZ+/yVg6l0= github.com/containers/buildah v1.32.0 h1:uz5Rcf7lGeStj7iPTBgO4UdhQYZqMMzyt9suDf16k1k= github.com/containers/buildah v1.32.0/go.mod h1:sN3rA3DbnqekNz3bNdkqWduuirYDuMs54LUCOZOomBE= -github.com/containers/common v0.56.1-0.20230927174306-9342cdd82aa6 h1:yALmokSK/0EKa12wV4AkgLr7ObDIo5sySJohDhE2UIs= -github.com/containers/common v0.56.1-0.20230927174306-9342cdd82aa6/go.mod h1:ABFEglmyt48WWWQv80kGhitfbVfR1Br35wk3gBQdrIk= +github.com/containers/common v0.56.1-0.20230929120646-4fd26cf84f19 h1:aAs/BiCzeNndwOTt0tnE26D4B4wtztVxl/4h0bu86qY= +github.com/containers/common v0.56.1-0.20230929120646-4fd26cf84f19/go.mod h1:yV1LzHyQ4KH99zPS4NA0cfKdZKG3RHjozjsiT0GPsIg= 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/gvisor-tap-vsock v0.7.1 h1:+Rc+sOPplrkQb/BUXeN0ug8TxjgyrIqo/9P/eNS2A4c= @@ -807,8 +807,8 @@ github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoT github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= -github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= diff --git a/pkg/domain/infra/abi/images_list.go b/pkg/domain/infra/abi/images_list.go index b980dcc001..d70fca268b 100644 --- a/pkg/domain/infra/abi/images_list.go +++ b/pkg/domain/infra/abi/images_list.go @@ -7,6 +7,7 @@ import ( "github.com/containers/common/libimage" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/util" ) func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) ([]*entities.ImageSummary, error) { @@ -14,7 +15,7 @@ func (ir *ImageEngine) List(ctx context.Context, opts entities.ImageListOptions) Filters: opts.Filter, SetListData: true, } - if !opts.All { + if !opts.All && !util.StringInSlice("intermediate=true", listImagesOptions.Filters) { // Filter intermediate images unless we want to list *all*. // NOTE: it's a positive filter, so `intermediate=false` means // to display non-intermediate images. diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index 971c48df6f..563715c8d1 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -118,6 +118,32 @@ var _ = Describe("Podman images", func() { Expect(session.OutputToStringArray()).To(HaveLen(2)) }) + It("podman images filter by image ID", func() { + session := podmanTest.Podman([]string{"inspect", ALPINE, "--format", "{{.ID}}"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToStringArray()).To(HaveLen(1)) + imgID := session.OutputToString() + + session = podmanTest.Podman([]string{"images", "--noheading", "--filter", "id=" + imgID[:5]}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToStringArray()).To(HaveLen(1)) + }) + + It("podman images filter by image digest", func() { + session := podmanTest.Podman([]string{"inspect", ALPINE, "--format", "{{.Digest}}"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToStringArray()).To(HaveLen(1)) + imgDigest := session.OutputToString() + + session = podmanTest.Podman([]string{"images", "--noheading", "--filter", "digest=" + imgDigest[:10]}) + session.WaitWithDefaultTimeout() + Expect(session).Should(ExitCleanly()) + Expect(session.OutputToStringArray()).To(HaveLen(1)) + }) + It("podman images filter reference", func() { result := podmanTest.Podman([]string{"images", "-q", "-f", "reference=quay.io/libpod/*"}) result.WaitWithDefaultTimeout() @@ -195,6 +221,18 @@ WORKDIR /test Expect(result.OutputToStringArray()).Should(BeEmpty(), "dangling image output: %q", result.OutputToString()) }) + It("podman images filter intermediate", func() { + dockerfile := `FROM quay.io/libpod/alpine:latest + RUN touch /tmp/test.txt + RUN touch /tmp/test-2.txt +` + podmanTest.BuildImage(dockerfile, "foobar.com/test-build", "true") + result := podmanTest.Podman([]string{"images", "--noheading", "--filter", "intermediate=true"}) + result.WaitWithDefaultTimeout() + Expect(result).Should(ExitCleanly()) + Expect(result.OutputToStringArray()).To(HaveLen(1)) + }) + It("podman pull by digest and list --all", func() { // Prevent regressing on issue #7651: error parsing name that includes a digest // component as if were a name that includes tag component. diff --git a/vendor/github.com/containers/common/libimage/filters.go b/vendor/github.com/containers/common/libimage/filters.go index fdde5e8326..88dfef9dca 100644 --- a/vendor/github.com/containers/common/libimage/filters.go +++ b/vendor/github.com/containers/common/libimage/filters.go @@ -11,7 +11,6 @@ import ( filtersPkg "github.com/containers/common/pkg/filters" "github.com/containers/common/pkg/timetype" "github.com/containers/image/v5/docker/reference" - "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" ) @@ -394,18 +393,17 @@ func filterDangling(ctx context.Context, value bool, tree *layerTree) filterFunc // filterID creates an image-ID filter for matching the specified value. func filterID(value string) filterFunc { return func(img *Image) (bool, error) { - return img.ID() == value, nil + return strings.HasPrefix(img.ID(), value), nil } } // filterDigest creates a digest filter for matching the specified value. func filterDigest(value string) (filterFunc, error) { - d, err := digest.Parse(value) - if err != nil { - return nil, fmt.Errorf("invalid value %q for digest filter: %w", value, err) + if !strings.HasPrefix(value, "sha256:") { + return nil, fmt.Errorf("invalid value %q for digest filter", value) } return func(img *Image) (bool, error) { - return img.hasDigest(d), nil + return img.containsDigestPrefix(value), nil }, nil } diff --git a/vendor/github.com/containers/common/libimage/image.go b/vendor/github.com/containers/common/libimage/image.go index 640968fd0e..e4198a792c 100644 --- a/vendor/github.com/containers/common/libimage/image.go +++ b/vendor/github.com/containers/common/libimage/image.go @@ -169,6 +169,17 @@ func (i *Image) hasDigest(wantedDigest digest.Digest) bool { return false } +// containsDigestPrefix returns whether the specified value matches any digest of the +// image. It checks for the prefix and not a full match. +func (i *Image) containsDigestPrefix(wantedDigestPrefix string) bool { + for _, d := range i.Digests() { + if strings.HasPrefix(d.String(), wantedDigestPrefix) { + return true + } + } + return false +} + // IsReadOnly returns whether the image is set read only. func (i *Image) IsReadOnly() bool { return i.storageImage.ReadOnly diff --git a/vendor/github.com/containers/common/libimage/manifest_list.go b/vendor/github.com/containers/common/libimage/manifest_list.go index 839892366b..6fed12d741 100644 --- a/vendor/github.com/containers/common/libimage/manifest_list.go +++ b/vendor/github.com/containers/common/libimage/manifest_list.go @@ -440,6 +440,8 @@ func (m *ManifestList) Push(ctx context.Context, destination string, options *Ma SignSigstorePrivateKeyPassphrase: options.SignSigstorePrivateKeyPassphrase, RemoveSignatures: options.RemoveSignatures, ManifestType: options.ManifestMIMEType, + MaxRetries: options.MaxRetries, + RetryDelay: options.RetryDelay, ForceCompressionFormat: options.ForceCompressionFormat, } diff --git a/vendor/github.com/containers/common/libimage/manifests/manifests.go b/vendor/github.com/containers/common/libimage/manifests/manifests.go index f3fb5b6aea..d28ac87bba 100644 --- a/vendor/github.com/containers/common/libimage/manifests/manifests.go +++ b/vendor/github.com/containers/common/libimage/manifests/manifests.go @@ -6,8 +6,10 @@ import ( "errors" "fmt" "io" + "time" "github.com/containers/common/pkg/manifests" + "github.com/containers/common/pkg/retry" "github.com/containers/common/pkg/supplemented" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/docker/reference" @@ -27,6 +29,10 @@ import ( "github.com/sirupsen/logrus" ) +const ( + defaultMaxRetries = 3 +) + const instancesData = "instances.json" // LookupReferenceFunc return an image reference based on the specified one. @@ -72,6 +78,11 @@ type PushOptions struct { SourceFilter LookupReferenceFunc // filter the list source AddCompression []string // add existing instances with requested compression algorithms to manifest list ForceCompressionFormat bool // force push with requested compression ignoring the blobs which can be reused. + // Maximum number of retries with exponential backoff when facing + // transient network errors. Default 3. + MaxRetries *uint + // RetryDelay used for the exponential back off of MaxRetries. + RetryDelay *time.Duration } // Create creates a new list containing information about the specified image, @@ -262,16 +273,31 @@ func (l *list) Push(ctx context.Context, dest types.ImageReference, options Push ForceCompressionFormat: options.ForceCompressionFormat, } + retryOptions := retry.Options{} + retryOptions.MaxRetry = defaultMaxRetries + if options.MaxRetries != nil { + retryOptions.MaxRetry = int(*options.MaxRetries) + } + if options.RetryDelay != nil { + retryOptions.Delay = *options.RetryDelay + } + // Copy whatever we were asked to copy. - manifestBytes, err := cp.Image(ctx, policyContext, dest, src, copyOptions) - if err != nil { - return nil, "", err + var manifestDigest digest.Digest + f := func() error { + opts := copyOptions + var manifestBytes []byte + var digest digest.Digest + var err error + if manifestBytes, err = cp.Image(ctx, policyContext, dest, src, opts); err == nil { + if digest, err = manifest.Digest(manifestBytes); err == nil { + manifestDigest = digest + } + } + return err } - manifestDigest, err := manifest.Digest(manifestBytes) - if err != nil { - return nil, "", err - } - return nil, manifestDigest, nil + err = retry.IfNecessary(ctx, f, &retryOptions) + return nil, manifestDigest, err } func prepareAddWithCompression(variants []string) ([]cp.OptionCompressionVariant, error) { diff --git a/vendor/github.com/containers/common/libnetwork/netavark/config.go b/vendor/github.com/containers/common/libnetwork/netavark/config.go index 45b49bf22b..de7af9575c 100644 --- a/vendor/github.com/containers/common/libnetwork/netavark/config.go +++ b/vendor/github.com/containers/common/libnetwork/netavark/config.go @@ -204,7 +204,10 @@ func (n *netavarkNetwork) networkCreate(newNetwork *types.Network, defaultNet bo } // rust only support "true" or "false" while go can parse 1 and 0 as well so we need to change it newNetwork.Options[types.NoDefaultRoute] = strconv.FormatBool(val) - + case types.VRFOption: + if len(value) == 0 { + return nil, errors.New("invalid vrf name") + } default: return nil, fmt.Errorf("unsupported bridge network option %s", key) } diff --git a/vendor/github.com/containers/common/libnetwork/types/const.go b/vendor/github.com/containers/common/libnetwork/types/const.go index 83103ef6ef..a916182007 100644 --- a/vendor/github.com/containers/common/libnetwork/types/const.go +++ b/vendor/github.com/containers/common/libnetwork/types/const.go @@ -43,6 +43,7 @@ const ( MetricOption = "metric" NoDefaultRoute = "no_default_route" BclimOption = "bclim" + VRFOption = "vrf" ) type NetworkBackend string diff --git a/vendor/github.com/containers/common/libnetwork/util/filters.go b/vendor/github.com/containers/common/libnetwork/util/filters.go index b348b33a35..70f90918c7 100644 --- a/vendor/github.com/containers/common/libnetwork/util/filters.go +++ b/vendor/github.com/containers/common/libnetwork/util/filters.go @@ -67,7 +67,7 @@ func createPruneFilterFuncs(key string, filterValues []string) (types.FilterFunc }, nil case "label!": return func(net types.Network) bool { - return !filters.MatchLabelFilters(filterValues, net.Labels) + return filters.MatchNegatedLabelFilters(filterValues, net.Labels) }, nil case "until": until, err := filters.ComputeUntilTimestamp(filterValues) diff --git a/vendor/github.com/containers/common/pkg/subscriptions/subscriptions.go b/vendor/github.com/containers/common/pkg/subscriptions/subscriptions.go index a2924737ec..6ba2154a77 100644 --- a/vendor/github.com/containers/common/pkg/subscriptions/subscriptions.go +++ b/vendor/github.com/containers/common/pkg/subscriptions/subscriptions.go @@ -362,6 +362,35 @@ func addFIPSModeSubscription(mounts *[]rspec.Mount, containerRunDir, mountPoint, } *mounts = append(*mounts, m) } + + // Make sure we set the config to FIPS so that the container does not overwrite + // /etc/crypto-policies/back-ends when crypto-policies-scripts is reinstalled. + cryptoPoliciesConfigFile := filepath.Join(containerRunDir, "fips-config") + file, err := os.Create(cryptoPoliciesConfigFile) + if err != nil { + return fmt.Errorf("creating fips config file in container for FIPS mode: %w", err) + } + defer file.Close() + if _, err := file.WriteString("FIPS\n"); err != nil { + return fmt.Errorf("writing fips config file in container for FIPS mode: %w", err) + } + if err = label.Relabel(cryptoPoliciesConfigFile, mountLabel, false); err != nil { + return fmt.Errorf("applying correct labels on fips-config file: %w", err) + } + if err := file.Chown(uid, gid); err != nil { + return fmt.Errorf("chown fips-config file: %w", err) + } + + policyConfig := "/etc/crypto-policies/config" + if !mountExists(*mounts, policyConfig) { + m := rspec.Mount{ + Source: cryptoPoliciesConfigFile, + Destination: policyConfig, + Type: "bind", + Options: []string{"bind", "rprivate"}, + } + *mounts = append(*mounts, m) + } return nil } diff --git a/vendor/github.com/onsi/gomega/CHANGELOG.md b/vendor/github.com/onsi/gomega/CHANGELOG.md index 1526497b9f..fbb12a157f 100644 --- a/vendor/github.com/onsi/gomega/CHANGELOG.md +++ b/vendor/github.com/onsi/gomega/CHANGELOG.md @@ -1,3 +1,18 @@ +## 1.28.0 + +### Features +- Add VerifyHost handler to ghttp (#698) [0b03b36] + +### Fixes +- Read Body for Newer Responses in HaveHTTPBodyMatcher (#686) [18d6673] + +### Maintenance +- Bump github.com/onsi/ginkgo/v2 from 2.11.0 to 2.12.0 (#693) [55a33f3] +- Typo in matchers.go (#691) [de68e8f] +- Bump commonmarker from 0.23.9 to 0.23.10 in /docs (#690) [ab17f5e] +- chore: update test matrix for Go 1.21 (#689) [5069017] +- Bump golang.org/x/net from 0.12.0 to 0.14.0 (#688) [babe25f] + ## 1.27.10 ### Fixes diff --git a/vendor/github.com/onsi/gomega/gomega_dsl.go b/vendor/github.com/onsi/gomega/gomega_dsl.go index 1fd1803acf..675a17840e 100644 --- a/vendor/github.com/onsi/gomega/gomega_dsl.go +++ b/vendor/github.com/onsi/gomega/gomega_dsl.go @@ -22,7 +22,7 @@ import ( "github.com/onsi/gomega/types" ) -const GOMEGA_VERSION = "1.27.10" +const GOMEGA_VERSION = "1.28.0" const nilGomegaPanic = `You are trying to make an assertion, but haven't registered Gomega's fail handler. If you're using Ginkgo then you probably forgot to put your assertion in an It(). diff --git a/vendor/github.com/onsi/gomega/matchers.go b/vendor/github.com/onsi/gomega/matchers.go index bdaf62b56b..4c13ad0a7b 100644 --- a/vendor/github.com/onsi/gomega/matchers.go +++ b/vendor/github.com/onsi/gomega/matchers.go @@ -94,7 +94,7 @@ func Succeed() types.GomegaMatcher { // // Expect(err).Should(MatchError("an error")) //asserts that err.Error() == "an error" // Expect(err).Should(MatchError(SomeError)) //asserts that err == SomeError (via reflect.DeepEqual) -// Expect(err).Should(MatchError(ContainSubstring("sprocket not found"))) // asserts that edrr.Error() contains substring "sprocket not found" +// Expect(err).Should(MatchError(ContainSubstring("sprocket not found"))) // asserts that err.Error() contains substring "sprocket not found" // // It is an error for err to be nil or an object that does not implement the // Error interface diff --git a/vendor/github.com/onsi/gomega/matchers/have_http_body_matcher.go b/vendor/github.com/onsi/gomega/matchers/have_http_body_matcher.go index 6a3dcdc353..d14d9e5fc6 100644 --- a/vendor/github.com/onsi/gomega/matchers/have_http_body_matcher.go +++ b/vendor/github.com/onsi/gomega/matchers/have_http_body_matcher.go @@ -11,8 +11,9 @@ import ( ) type HaveHTTPBodyMatcher struct { - Expected interface{} - cachedBody []byte + Expected interface{} + cachedResponse interface{} + cachedBody []byte } func (matcher *HaveHTTPBodyMatcher) Match(actual interface{}) (bool, error) { @@ -73,7 +74,7 @@ func (matcher *HaveHTTPBodyMatcher) NegatedFailureMessage(actual interface{}) (m // the Reader is closed and it is not readable again in FailureMessage() // or NegatedFailureMessage() func (matcher *HaveHTTPBodyMatcher) body(actual interface{}) ([]byte, error) { - if matcher.cachedBody != nil { + if matcher.cachedResponse == actual && matcher.cachedBody != nil { return matcher.cachedBody, nil } @@ -91,8 +92,10 @@ func (matcher *HaveHTTPBodyMatcher) body(actual interface{}) ([]byte, error) { switch a := actual.(type) { case *http.Response: + matcher.cachedResponse = a return body(a) case *httptest.ResponseRecorder: + matcher.cachedResponse = a return body(a.Result()) default: return nil, fmt.Errorf("HaveHTTPBody matcher expects *http.Response or *httptest.ResponseRecorder. Got:\n%s", format.Object(actual, 1)) diff --git a/vendor/modules.txt b/vendor/modules.txt index 442c61fbea..32665492e9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -164,7 +164,7 @@ github.com/containers/buildah/pkg/sshagent github.com/containers/buildah/pkg/util github.com/containers/buildah/pkg/volumes github.com/containers/buildah/util -# github.com/containers/common v0.56.1-0.20230927174306-9342cdd82aa6 +# github.com/containers/common v0.56.1-0.20230929120646-4fd26cf84f19 ## explicit; go 1.18 github.com/containers/common/libimage github.com/containers/common/libimage/define @@ -797,7 +797,7 @@ github.com/onsi/ginkgo/v2/internal/parallel_support github.com/onsi/ginkgo/v2/internal/testingtproxy github.com/onsi/ginkgo/v2/reporters github.com/onsi/ginkgo/v2/types -# github.com/onsi/gomega v1.27.10 +# github.com/onsi/gomega v1.28.0 ## explicit; go 1.18 github.com/onsi/gomega github.com/onsi/gomega/format