diff options
Diffstat (limited to 'pkg/adapter')
-rw-r--r-- | pkg/adapter/checkpoint_restore.go | 153 | ||||
-rw-r--r-- | pkg/adapter/containers.go | 20 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 6 | ||||
-rw-r--r-- | pkg/adapter/errors.go | 2 | ||||
-rw-r--r-- | pkg/adapter/images_remote.go | 2 | ||||
-rw-r--r-- | pkg/adapter/info_remote.go | 4 | ||||
-rw-r--r-- | pkg/adapter/network.go | 6 | ||||
-rw-r--r-- | pkg/adapter/pods.go | 13 | ||||
-rw-r--r-- | pkg/adapter/pods_remote.go | 2 | ||||
-rw-r--r-- | pkg/adapter/reset_remote.go | 2 | ||||
-rw-r--r-- | pkg/adapter/runtime.go | 35 | ||||
-rw-r--r-- | pkg/adapter/runtime_remote.go | 52 | ||||
-rw-r--r-- | pkg/adapter/terminal_linux.go | 5 | ||||
-rw-r--r-- | pkg/adapter/terminal_unsupported.go | 2 |
14 files changed, 77 insertions, 227 deletions
diff --git a/pkg/adapter/checkpoint_restore.go b/pkg/adapter/checkpoint_restore.go deleted file mode 100644 index 7f80b782a..000000000 --- a/pkg/adapter/checkpoint_restore.go +++ /dev/null @@ -1,153 +0,0 @@ -// +build !remoteclient - -package adapter - -import ( - "context" - "io/ioutil" - "os" - "path/filepath" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/errorhandling" - "github.com/containers/libpod/pkg/util" - "github.com/containers/storage/pkg/archive" - jsoniter "github.com/json-iterator/go" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// Prefixing the checkpoint/restore related functions with 'cr' - -// crImportFromJSON imports the JSON files stored in the exported -// checkpoint tarball -func crImportFromJSON(filePath string, v interface{}) error { - jsonFile, err := os.Open(filePath) - if err != nil { - return errors.Wrapf(err, "Failed to open container definition %s for restore", filePath) - } - defer errorhandling.CloseQuiet(jsonFile) - - content, err := ioutil.ReadAll(jsonFile) - if err != nil { - return errors.Wrapf(err, "Failed to read container definition %s for restore", filePath) - } - json := jsoniter.ConfigCompatibleWithStandardLibrary - if err = json.Unmarshal(content, v); err != nil { - return errors.Wrapf(err, "Failed to unmarshal container definition %s for restore", filePath) - } - - return nil -} - -// crImportCheckpoint it the function which imports the information -// from checkpoint tarball and re-creates the container from that information -func crImportCheckpoint(ctx context.Context, runtime *libpod.Runtime, input string, name string) ([]*libpod.Container, error) { - // First get the container definition from the - // tarball to a temporary directory - archiveFile, err := os.Open(input) - if err != nil { - return nil, errors.Wrapf(err, "Failed to open checkpoint archive %s for import", input) - } - defer errorhandling.CloseQuiet(archiveFile) - options := &archive.TarOptions{ - // Here we only need the files config.dump and spec.dump - ExcludePatterns: []string{ - "checkpoint", - "artifacts", - "ctr.log", - "rootfs-diff.tar", - "network.status", - "deleted.files", - }, - } - dir, err := ioutil.TempDir("", "checkpoint") - if err != nil { - return nil, err - } - defer func() { - if err := os.RemoveAll(dir); err != nil { - logrus.Errorf("could not recursively remove %s: %q", dir, err) - } - }() - err = archive.Untar(archiveFile, dir, options) - if err != nil { - return nil, errors.Wrapf(err, "Unpacking of checkpoint archive %s failed", input) - } - - // Load spec.dump from temporary directory - dumpSpec := new(spec.Spec) - if err := crImportFromJSON(filepath.Join(dir, "spec.dump"), dumpSpec); err != nil { - return nil, err - } - - // Load config.dump from temporary directory - config := new(libpod.ContainerConfig) - if err = crImportFromJSON(filepath.Join(dir, "config.dump"), config); err != nil { - return nil, err - } - - // This should not happen as checkpoints with these options are not exported. - if (len(config.Dependencies) > 0) || (len(config.NamedVolumes) > 0) { - return nil, errors.Errorf("Cannot import checkpoints of containers with named volumes or dependencies") - } - - ctrID := config.ID - newName := false - - // Check if the restored container gets a new name - if name != "" { - config.ID = "" - config.Name = name - newName = true - } - - ctrName := config.Name - - // The code to load the images is copied from create.go - // In create.go this only set if '--quiet' does not exist. - writer := os.Stderr - rtc, err := runtime.GetConfig() - if err != nil { - return nil, err - } - - _, err = runtime.ImageRuntime().New(ctx, config.RootfsImageName, rtc.SignaturePolicyPath, "", writer, nil, image.SigningOptions{}, nil, util.PullImageMissing) - if err != nil { - return nil, err - } - - // Now create a new container from the just loaded information - container, err := runtime.RestoreContainer(ctx, dumpSpec, config) - if err != nil { - return nil, err - } - - var containers []*libpod.Container - if container == nil { - return nil, nil - } - - containerConfig := container.Config() - if containerConfig.Name != ctrName { - return nil, errors.Errorf("Name of restored container (%s) does not match requested name (%s)", containerConfig.Name, ctrName) - } - - if !newName { - // Only check ID for a restore with the same name. - // Using -n to request a new name for the restored container, will also create a new ID - if containerConfig.ID != ctrID { - return nil, errors.Errorf("ID of restored container (%s) does not match requested ID (%s)", containerConfig.ID, ctrID) - } - } - - // Check if the ExitCommand points to the correct container ID - if containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1] != containerConfig.ID { - return nil, errors.Errorf("'ExitCommandID' uses ID %s instead of container ID %s", containerConfig.ExitCommand[len(containerConfig.ExitCommand)-1], containerConfig.ID) - } - - containers = append(containers, container) - return containers, nil -} diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 0d2ca1a64..ecadbd2f9 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -16,6 +16,7 @@ import ( "time" "github.com/containers/buildah" + cfg "github.com/containers/common/pkg/config" "github.com/containers/image/v5/manifest" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" @@ -25,6 +26,7 @@ import ( "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/logs" "github.com/containers/libpod/pkg/adapter/shortcuts" + "github.com/containers/libpod/pkg/checkpoint" envLib "github.com/containers/libpod/pkg/env" "github.com/containers/libpod/pkg/systemd/generate" "github.com/containers/storage" @@ -380,11 +382,11 @@ func (r *LocalRuntime) selectDetachKeys(flagValue string) (string, error) { if err != nil { return "", errors.Wrapf(err, "unable to retrieve runtime config") } - if config.DetachKeys != "" { - return config.DetachKeys, nil + if config.Engine.DetachKeys != "" { + return config.Engine.DetachKeys, nil } - return define.DefaultDetachKeys, nil + return cfg.DefaultDetachKeys, nil } // Run a libpod container @@ -624,7 +626,7 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) switch { case c.Import != "": - containers, err = crImportCheckpoint(ctx, r.Runtime, c.Import, c.Name) + containers, err = checkpoint.CRImportCheckpoint(ctx, r.Runtime, c.Import, c.Name) case c.All: containers, err = r.GetContainers(filterFuncs...) default: @@ -1002,7 +1004,7 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal } env = envLib.Join(env, cliEnv) - streams := new(libpod.AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = os.Stdout streams.ErrorStream = os.Stderr if cli.Interactive { @@ -1212,8 +1214,8 @@ func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSyst return nil, false, err } - timeout := int(ctr.StopTimeout()) - if c.StopTimeout >= 0 { + timeout := ctr.StopTimeout() + if c.Flags().Changed("timeout") || c.Flags().Changed("time") { timeout = c.StopTimeout } @@ -1369,9 +1371,9 @@ func (r *LocalRuntime) Commit(ctx context.Context, c *cliconfig.CommitValues, co return "", err } - sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false) + sc := image.GetSystemContext(rtc.Engine.SignaturePolicyPath, "", false) coptions := buildah.CommitOptions{ - SignaturePolicyPath: rtc.SignaturePolicyPath, + SignaturePolicyPath: rtc.Engine.SignaturePolicyPath, ReportWriter: writer, SystemContext: sc, PreferredManifestType: mimeType, diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 32a84b60d..fc8b524d6 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -15,11 +15,11 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/logs" envLib "github.com/containers/libpod/pkg/env" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/libpod/pkg/varlinkapi/virtwriter" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/docker/pkg/term" @@ -32,12 +32,12 @@ import ( ) // Inspect returns an inspect struct from varlink -func (c *Container) Inspect(size bool) (*libpod.InspectContainerData, error) { +func (c *Container) Inspect(size bool) (*define.InspectContainerData, error) { reply, err := iopodman.ContainerInspectData().Call(c.Runtime.Conn, c.ID(), size) if err != nil { return nil, err } - data := libpod.InspectContainerData{} + data := define.InspectContainerData{} if err := json.Unmarshal([]byte(reply), &data); err != nil { return nil, err } diff --git a/pkg/adapter/errors.go b/pkg/adapter/errors.go index ede3d4b1a..012d01d39 100644 --- a/pkg/adapter/errors.go +++ b/pkg/adapter/errors.go @@ -3,8 +3,8 @@ package adapter import ( - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod/define" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/pkg/errors" ) diff --git a/pkg/adapter/images_remote.go b/pkg/adapter/images_remote.go index e7b38dccc..2df0ffcde 100644 --- a/pkg/adapter/images_remote.go +++ b/pkg/adapter/images_remote.go @@ -6,8 +6,8 @@ import ( "context" "encoding/json" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/pkg/inspect" + iopodman "github.com/containers/libpod/pkg/varlink" ) // Inspect returns returns an ImageData struct from over a varlink connection diff --git a/pkg/adapter/info_remote.go b/pkg/adapter/info_remote.go index c55d1f6ef..0e8fb06d1 100644 --- a/pkg/adapter/info_remote.go +++ b/pkg/adapter/info_remote.go @@ -4,9 +4,9 @@ package adapter import ( "encoding/json" - "github.com/containers/libpod/libpod/define" - "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/define" + iopodman "github.com/containers/libpod/pkg/varlink" ) // Info returns information for the host system and its components diff --git a/pkg/adapter/network.go b/pkg/adapter/network.go index b25f54a13..577ffe19f 100644 --- a/pkg/adapter/network.go +++ b/pkg/adapter/network.go @@ -23,9 +23,9 @@ func getCNIConfDir(r *LocalRuntime) (string, error) { if err != nil { return "", err } - configPath := config.CNIConfigDir + configPath := config.Network.NetworkConfigDir - if len(config.CNIConfigDir) < 1 { + if len(config.Network.NetworkConfigDir) < 1 { configPath = network.CNIConfigDir } return configPath, nil @@ -211,7 +211,7 @@ func (r *LocalRuntime) NetworkCreateBridge(cli *cliconfig.NetworkCreateValues) ( plugins = append(plugins, network.NewPortMapPlugin()) plugins = append(plugins, network.NewFirewallPlugin()) // if we find the dnsname plugin, we add configuration for it - if network.HasDNSNamePlugin(runtimeConfig.CNIPluginDir) && !cli.DisableDNS { + if network.HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !cli.DisableDNS { // Note: in the future we might like to allow for dynamic domain names plugins = append(plugins, network.NewDNSNamePlugin(network.DefaultPodmanDomainName)) } diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 64ebd3954..7c2a84cc7 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -784,6 +784,12 @@ func getPodPorts(containers []v1.Container) []ocicni.PortMapping { var infraPorts []ocicni.PortMapping for _, container := range containers { for _, p := range container.Ports { + if p.HostPort != 0 && p.ContainerPort == 0 { + p.ContainerPort = p.HostPort + } + if p.Protocol == "" { + p.Protocol = "tcp" + } portBinding := ocicni.PortMapping{ HostPort: p.HostPort, ContainerPort: p.ContainerPort, @@ -792,7 +798,12 @@ func getPodPorts(containers []v1.Container) []ocicni.PortMapping { if p.HostIP != "" { logrus.Debug("HostIP on port bindings is not supported") } - infraPorts = append(infraPorts, portBinding) + // only hostPort is utilized in podman context, all container ports + // are accessible inside the shared network namespace + if p.HostPort != 0 { + infraPorts = append(infraPorts, portBinding) + } + } } return infraPorts diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index 6b8f22f15..ebd10a92a 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -10,9 +10,9 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/libpod/pkg/varlinkapi" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/pkg/adapter/reset_remote.go b/pkg/adapter/reset_remote.go index 663fab639..284b54a17 100644 --- a/pkg/adapter/reset_remote.go +++ b/pkg/adapter/reset_remote.go @@ -3,7 +3,7 @@ package adapter import ( - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/pkg/varlink" ) // Info returns information for the host system and its components diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 7817a1f98..7a181e7e5 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -13,7 +13,6 @@ import ( "github.com/containers/buildah" "github.com/containers/buildah/imagebuildah" "github.com/containers/buildah/pkg/formats" - "github.com/containers/buildah/pkg/parse" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/cliconfig" @@ -296,37 +295,13 @@ func libpodVolumeToVolume(volumes []*libpod.Volume) []*Volume { // Build is the wrapper to build images func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, options imagebuildah.BuildOptions, dockerfiles []string) (string, reference.Canonical, error) { - namespaceOptions, networkPolicy, err := parse.NamespaceOptions(c.PodmanCommand.Command) - if err != nil { - return "", nil, errors.Wrapf(err, "error parsing namespace-related options") - } - usernsOption, idmappingOptions, err := parse.IDMappingOptions(c.PodmanCommand.Command, options.Isolation) - if err != nil { - return "", nil, errors.Wrapf(err, "error parsing ID mapping options") - } - namespaceOptions.AddOrReplace(usernsOption...) - - systemContext, err := parse.SystemContextFromOptions(c.PodmanCommand.Command) - if err != nil { - return "", nil, errors.Wrapf(err, "error building system context") - } authfile := c.Authfile if len(c.Authfile) == 0 { authfile = os.Getenv("REGISTRY_AUTH_FILE") } - systemContext.AuthFilePath = authfile - commonOpts, err := parse.CommonBuildOptions(c.PodmanCommand.Command) - if err != nil { - return "", nil, err - } - - options.NamespaceOptions = namespaceOptions - options.ConfigureNetwork = networkPolicy - options.IDMappingOptions = idmappingOptions - options.CommonBuildOpts = commonOpts - options.SystemContext = systemContext + options.SystemContext.AuthFilePath = authfile if c.GlobalFlags.Runtime != "" { options.Runtime = c.GlobalFlags.Runtime @@ -356,11 +331,11 @@ func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { errs = append(errs, err) return vids, errs } - for _, r := range reports { - if r.Err == nil { - vids = append(vids, r.Id) + for k, v := range reports { + if v == nil { + vids = append(vids, k) } else { - errs = append(errs, r.Err) + errs = append(errs, v) } } return vids, errs diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index fc396eddb..a4ac660ea 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -17,16 +17,17 @@ import ( "github.com/containers/buildah/imagebuildah" "github.com/containers/buildah/pkg/formats" + "github.com/containers/common/pkg/config" "github.com/containers/image/v5/docker/reference" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/remoteclientconfig" - iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/util" + iopodman "github.com/containers/libpod/pkg/varlink" "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" "github.com/opencontainers/go-digest" @@ -113,15 +114,20 @@ func (r RemoteRuntime) DeferredShutdown(force bool) { } } -// RuntimeConfig is a bogus wrapper for compat with the libpod runtime -type RuntimeConfig struct { +// Containers is a bogus wrapper for compat with the libpod runtime +type ContainersConfig struct { // CGroupManager is the CGroup Manager to use // Valid values are "cgroupfs" and "systemd" CgroupManager string } +// RuntimeConfig is a bogus wrapper for compat with the libpod runtime +type RuntimeConfig struct { + Containers ContainersConfig +} + // Shutdown is a bogus wrapper for compat with the libpod runtime -func (r *RemoteRuntime) GetConfig() (*RuntimeConfig, error) { +func (r *RemoteRuntime) GetConfig() (*config.Config, error) { return nil, nil } @@ -535,32 +541,40 @@ func (r *LocalRuntime) Build(ctx context.Context, c *cliconfig.BuildValues, opti Ulimit: options.CommonBuildOpts.Ulimit, Volume: options.CommonBuildOpts.Volumes, } - buildinfo := iopodman.BuildInfo{ - AdditionalTags: options.AdditionalTags, - Annotations: options.Annotations, - BuildArgs: options.Args, - BuildOptions: buildOptions, - CniConfigDir: options.CNIConfigDir, - CniPluginDir: options.CNIPluginPath, - Compression: string(options.Compression), - DefaultsMountFilePath: options.DefaultMountsFilePath, - Dockerfiles: dockerfiles, // Err: string(options.Err), + // Out: + // ReportWriter: + Architecture: options.Architecture, + AddCapabilities: options.AddCapabilities, + AdditionalTags: options.AdditionalTags, + Annotations: options.Annotations, + BuildArgs: options.Args, + BuildOptions: buildOptions, + CniConfigDir: options.CNIConfigDir, + CniPluginDir: options.CNIPluginPath, + Compression: string(options.Compression), + Devices: options.Devices, + DefaultsMountFilePath: options.DefaultMountsFilePath, + Dockerfiles: dockerfiles, + DropCapabilities: options.DropCapabilities, ForceRmIntermediateCtrs: options.ForceRmIntermediateCtrs, Iidfile: options.IIDFile, Label: options.Labels, Layers: options.Layers, - Nocache: options.NoCache, - // Out: + // NamespaceOptions: options.NamespaceOptions, + Nocache: options.NoCache, + Os: options.OS, Output: options.Output, OutputFormat: options.OutputFormat, PullPolicy: options.PullPolicy.String(), Quiet: options.Quiet, RemoteIntermediateCtrs: options.RemoveIntermediateCtrs, - // ReportWriter: - RuntimeArgs: options.RuntimeArgs, - Squash: options.Squash, + RuntimeArgs: options.RuntimeArgs, + SignBy: options.SignBy, + Squash: options.Squash, + Target: options.Target, + TransientMounts: options.TransientMounts, } // tar the file outputFile, err := ioutil.TempFile("", "varlink_tar_send") diff --git a/pkg/adapter/terminal_linux.go b/pkg/adapter/terminal_linux.go index ef5a6f926..a56704be6 100644 --- a/pkg/adapter/terminal_linux.go +++ b/pkg/adapter/terminal_linux.go @@ -7,6 +7,7 @@ import ( "os" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/crypto/ssh/terminal" @@ -14,7 +15,7 @@ import ( ) // ExecAttachCtr execs and attaches to a container -func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { resize := make(chan remotecommand.TerminalSize) haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd())) @@ -69,7 +70,7 @@ func StartAttachCtr(ctx context.Context, ctr *libpod.Container, stdout, stderr, defer cancel() } - streams := new(libpod.AttachStreams) + streams := new(define.AttachStreams) streams.OutputStream = stdout streams.ErrorStream = stderr streams.InputStream = bufio.NewReader(stdin) diff --git a/pkg/adapter/terminal_unsupported.go b/pkg/adapter/terminal_unsupported.go index 3009f0a38..9067757a1 100644 --- a/pkg/adapter/terminal_unsupported.go +++ b/pkg/adapter/terminal_unsupported.go @@ -11,7 +11,7 @@ import ( ) // ExecAttachCtr execs and attaches to a container -func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *libpod.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { +func ExecAttachCtr(ctx context.Context, ctr *libpod.Container, tty, privileged bool, env map[string]string, cmd []string, user, workDir string, streams *define.AttachStreams, preserveFDs uint, detachKeys string) (int, error) { return -1, define.ErrNotImplemented } |