From bcd5128207fa60a24064c8f97c45fabd85fc231a Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Tue, 22 Sep 2020 09:32:59 -0700 Subject: Add Server header to API service responses Aids in reading logs of different services Signed-off-by: Jhon Honce --- pkg/api/server/handler_api.go | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'pkg') diff --git a/pkg/api/server/handler_api.go b/pkg/api/server/handler_api.go index f2ce0301b..920811c51 100644 --- a/pkg/api/server/handler_api.go +++ b/pkg/api/server/handler_api.go @@ -34,15 +34,18 @@ func (s *APIServer) APIHandler(h http.HandlerFunc) http.HandlerFunc { } // TODO: Use r.ConnContext when ported to go 1.13 - c := context.WithValue(r.Context(), "decoder", s.Decoder) //nolint - c = context.WithValue(c, "runtime", s.Runtime) //nolint - c = context.WithValue(c, "shutdownFunc", s.Shutdown) //nolint - c = context.WithValue(c, "idletracker", s.idleTracker) //nolint + c := context.WithValue(r.Context(), "decoder", s.Decoder) // nolint + c = context.WithValue(c, "runtime", s.Runtime) // nolint + c = context.WithValue(c, "shutdownFunc", s.Shutdown) // nolint + c = context.WithValue(c, "idletracker", s.idleTracker) // nolint r = r.WithContext(c) - v := utils.APIVersion[utils.CompatTree][utils.CurrentAPIVersion] - w.Header().Set("API-Version", fmt.Sprintf("%d.%d", v.Major, v.Minor)) - w.Header().Set("Libpod-API-Version", utils.APIVersion[utils.LibpodTree][utils.CurrentAPIVersion].String()) + cv := utils.APIVersion[utils.CompatTree][utils.CurrentAPIVersion] + w.Header().Set("API-Version", fmt.Sprintf("%d.%d", cv.Major, cv.Minor)) + + lv := utils.APIVersion[utils.LibpodTree][utils.CurrentAPIVersion].String() + w.Header().Set("Libpod-API-Version", lv) + w.Header().Set("Server", "Libpod/"+lv+" ("+runtime.GOOS+")") h(w, r) } -- cgit v1.2.3-54-g00ecf From 8252fd24c1f71e3c87f84fdf12474629d939ce34 Mon Sep 17 00:00:00 2001 From: baude Date: Mon, 21 Sep 2020 12:31:07 -0500 Subject: add missing return for compat kill on an error condition in kill for the compatibility layer, we were missing a return. Signed-off-by: baude --- pkg/api/handlers/compat/containers.go | 1 + 1 file changed, 1 insertion(+) (limited to 'pkg') diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index b1ef08cda..1c2356b92 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -175,6 +175,7 @@ func KillContainer(w http.ResponseWriter, r *http.Request) { err = con.Kill(signal) if err != nil { utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name)) + return } // Docker waits for the container to stop if the signal is 0 or -- cgit v1.2.3-54-g00ecf From c06dfe4ea666535ace8c46abfb7443a738099339 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Wed, 23 Sep 2020 09:11:16 -0400 Subject: Remove final v2remotefail failures Most have been fixed, others I replaced with SkipIfRemote Fix ContainerStart on tunnel, it needs to wait for the exit status before returning. Signed-off-by: Daniel J Walsh --- pkg/domain/infra/tunnel/containers.go | 62 ++++++++++++++++++++++++++++------- pkg/domain/infra/tunnel/events.go | 32 ++++++++++++++++++ test/e2e/config.go | 4 --- test/e2e/create_test.go | 5 +-- test/e2e/exec_test.go | 5 ++- test/e2e/healthcheck_run_test.go | 1 - test/e2e/images_test.go | 8 ++--- test/e2e/start_test.go | 6 ++-- 8 files changed, 93 insertions(+), 30 deletions(-) (limited to 'pkg') diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index d0f90d900..7ea3b16a6 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -481,27 +481,67 @@ func startAndAttach(ic *ContainerEngine, name string, detachKeys *string, input, func (ic *ContainerEngine) ContainerStart(ctx context.Context, namesOrIds []string, options entities.ContainerStartOptions) ([]*entities.ContainerStartReport, error) { reports := []*entities.ContainerStartReport{} - for _, name := range namesOrIds { + var exitCode = define.ExecErrorCodeGeneric + ctrs, err := getContainersByContext(ic.ClientCxt, false, false, namesOrIds) + if err != nil { + return nil, err + } + // There can only be one container if attach was used + for i, ctr := range ctrs { + name := ctr.ID report := entities.ContainerStartReport{ Id: name, - RawInput: name, - ExitCode: 125, + RawInput: namesOrIds[i], + ExitCode: exitCode, } + ctrRunning := ctr.State == define.ContainerStateRunning.String() if options.Attach { - report.Err = startAndAttach(ic, name, &options.DetachKeys, options.Stdin, options.Stdout, options.Stderr) - if report.Err == nil { - exitCode, err := containers.Wait(ic.ClientCxt, name, nil) - if err == nil { - report.ExitCode = int(exitCode) + err = startAndAttach(ic, name, &options.DetachKeys, options.Stdin, options.Stdout, options.Stderr) + if err == define.ErrDetach { + // User manually detached + // Exit cleanly immediately + report.Err = err + reports = append(reports, &report) + return reports, nil + } + if ctrRunning { + reports = append(reports, &report) + return reports, nil + } + + if err != nil { + report.ExitCode = define.ExitCode(report.Err) + report.Err = err + reports = append(reports, &report) + return reports, errors.Wrapf(report.Err, "unable to start container %s", name) + } + exitCode, err := containers.Wait(ic.ClientCxt, name, nil) + if err == define.ErrNoSuchCtr { + // Check events + event, err := ic.GetLastContainerEvent(ctx, name, events.Exited) + if err != nil { + logrus.Errorf("Cannot get exit code: %v", err) + report.ExitCode = define.ExecErrorCodeNotFound + } else { + report.ExitCode = event.ContainerExitCode } } else { - report.ExitCode = define.ExitCode(report.Err) + report.ExitCode = int(exitCode) } reports = append(reports, &report) return reports, nil } - report.Err = containers.Start(ic.ClientCxt, name, &options.DetachKeys) - report.ExitCode = define.ExitCode(report.Err) + // Start the container if it's not running already. + if !ctrRunning { + err = containers.Start(ic.ClientCxt, name, &options.DetachKeys) + if err != nil { + report.Err = errors.Wrapf(err, "unable to start container %q", name) + report.ExitCode = define.ExitCode(err) + reports = append(reports, &report) + continue + } + } + report.ExitCode = 0 reports = append(reports, &report) } return reports, nil diff --git a/pkg/domain/infra/tunnel/events.go b/pkg/domain/infra/tunnel/events.go index e6f4834b9..53bae6cef 100644 --- a/pkg/domain/infra/tunnel/events.go +++ b/pkg/domain/infra/tunnel/events.go @@ -2,8 +2,10 @@ package tunnel import ( "context" + // "fmt" "strings" + "github.com/containers/podman/v2/libpod/events" "github.com/containers/podman/v2/pkg/bindings/system" "github.com/containers/podman/v2/pkg/domain/entities" "github.com/pkg/errors" @@ -29,3 +31,33 @@ func (ic *ContainerEngine) Events(ctx context.Context, opts entities.EventsOptio }() return system.Events(ic.ClientCxt, binChan, nil, &opts.Since, &opts.Until, filters, &opts.Stream) } + +// GetLastContainerEvent takes a container name or ID and an event status and returns +// the last occurrence of the container event +func (ic *ContainerEngine) GetLastContainerEvent(ctx context.Context, nameOrID string, containerEvent events.Status) (*events.Event, error) { + // check to make sure the event.Status is valid + if _, err := events.StringToStatus(containerEvent.String()); err != nil { + return nil, err + } + var event events.Event + return &event, nil + + /* + FIXME: We need new bindings for this section + filters := []string{ + fmt.Sprintf("container=%s", nameOrID), + fmt.Sprintf("event=%s", containerEvent), + "type=container", + } + + containerEvents, err := system.GetEvents(ctx, entities.EventsOptions{Filter: filters}) + if err != nil { + return nil, err + } + if len(containerEvents) < 1 { + return nil, errors.Wrapf(events.ErrEventNotFound, "%s not found", containerEvent.String()) + } + // return the last element in the slice + return containerEvents[len(containerEvents)-1], nil + */ +} diff --git a/test/e2e/config.go b/test/e2e/config.go index 71c4dee31..0e1850614 100644 --- a/test/e2e/config.go +++ b/test/e2e/config.go @@ -27,8 +27,4 @@ var ( // v2fail is a temporary variable to help us track // tests that fail in v2 v2fail = "does not pass integration tests with v2 podman" - - // v2remotefail is a temporary variable to help us track - // tests that fail in v2 remote - v2remotefail = "does not pass integration tests with v2 podman remote" ) diff --git a/test/e2e/create_test.go b/test/e2e/create_test.go index 6022be5f6..3a92f16c4 100644 --- a/test/e2e/create_test.go +++ b/test/e2e/create_test.go @@ -108,13 +108,12 @@ var _ = Describe("Podman create", func() { }) It("podman create --entrypoint \"\"", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"create", "--entrypoint", "", ALPINE, "ls"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) Expect(podmanTest.NumberOfContainers()).To(Equal(1)) - result := podmanTest.Podman([]string{"inspect", "-l", "--format", "{{.Config.Entrypoint}}"}) + result := podmanTest.Podman([]string{"inspect", session.OutputToString(), "--format", "{{.Config.Entrypoint}}"}) result.WaitWithDefaultTimeout() Expect(result.ExitCode()).To(Equal(0)) Expect(result.OutputToString()).To(Equal("")) @@ -134,7 +133,6 @@ var _ = Describe("Podman create", func() { }) It("podman create --mount flag with multiple mounts", func() { - Skip(v2remotefail) vol1 := filepath.Join(podmanTest.TempDir, "vol-test1") err := os.MkdirAll(vol1, 0755) Expect(err).To(BeNil()) @@ -160,7 +158,6 @@ var _ = Describe("Podman create", func() { if podmanTest.Host.Arch == "ppc64le" { Skip("skip failing test on ppc64le") } - Skip(v2remotefail) mountPath := filepath.Join(podmanTest.TempDir, "secrets") os.Mkdir(mountPath, 0755) session := podmanTest.Podman([]string{"create", "--name", "test", "--mount", fmt.Sprintf("type=bind,src=%s,target=/create/test", mountPath), ALPINE, "grep", "/create/test", "/proc/self/mountinfo"}) diff --git a/test/e2e/exec_test.go b/test/e2e/exec_test.go index 6841aa5a2..99a41ff3b 100644 --- a/test/e2e/exec_test.go +++ b/test/e2e/exec_test.go @@ -122,13 +122,12 @@ var _ = Describe("Podman exec", func() { }) It("podman exec terminal doesn't hang", func() { - Skip(v2remotefail) - setup := podmanTest.Podman([]string{"run", "-dti", fedoraMinimal, "sleep", "+Inf"}) + setup := podmanTest.Podman([]string{"run", "-dti", "--name", "test1", fedoraMinimal, "sleep", "+Inf"}) setup.WaitWithDefaultTimeout() Expect(setup.ExitCode()).To(Equal(0)) for i := 0; i < 5; i++ { - session := podmanTest.Podman([]string{"exec", "-lti", "true"}) + session := podmanTest.Podman([]string{"exec", "-ti", "test1", "true"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) } diff --git a/test/e2e/healthcheck_run_test.go b/test/e2e/healthcheck_run_test.go index c020860ea..71e73af9c 100644 --- a/test/e2e/healthcheck_run_test.go +++ b/test/e2e/healthcheck_run_test.go @@ -174,7 +174,6 @@ var _ = Describe("Podman healthcheck run", func() { }) It("podman healthcheck single healthy result changes failed to healthy", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"run", "-dt", "--name", "hc", "--health-retries", "2", "--health-cmd", "ls /foo || exit 1", ALPINE, "top"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go index ddf2e20b8..d9ad10fe9 100644 --- a/test/e2e/images_test.go +++ b/test/e2e/images_test.go @@ -176,7 +176,7 @@ var _ = Describe("Podman images", func() { }) It("podman images filter before image", func() { - Skip(v2remotefail) + SkipIfRemote("FIXME This should work on podman-remote") dockerfile := `FROM docker.io/library/alpine:latest RUN apk update && apk add strace ` @@ -189,7 +189,6 @@ RUN apk update && apk add strace }) It("podman images workingdir from image", func() { - Skip(v2remotefail) dockerfile := `FROM docker.io/library/alpine:latest WORKDIR /test ` @@ -341,7 +340,7 @@ WORKDIR /test }) It("podman images --all flag", func() { - Skip(v2remotefail) + SkipIfRemote("FIXME This should work on podman-remote") podmanTest.RestoreAllArtifacts() dockerfile := `FROM docker.io/library/alpine:latest RUN mkdir hello @@ -361,7 +360,6 @@ ENV foo=bar }) It("podman images filter by label", func() { - Skip(v2remotefail) dockerfile := `FROM docker.io/library/alpine:latest LABEL version="1.0" LABEL "com.example.vendor"="Example Vendor" @@ -374,7 +372,7 @@ LABEL "com.example.vendor"="Example Vendor" }) It("podman with images with no layers", func() { - Skip(v2remotefail) + SkipIfRemote("FIXME This should work on podman-remote") dockerfile := strings.Join([]string{ `FROM scratch`, `LABEL org.opencontainers.image.authors=""`, diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index aef5ca001..850744308 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -132,14 +132,16 @@ var _ = Describe("Podman start", func() { }) It("podman failed to start with --rm should delete the container", func() { - Skip(v2remotefail) session := podmanTest.Podman([]string{"create", "--name", "test1", "-it", "--rm", ALPINE, "foo"}) session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) start := podmanTest.Podman([]string{"start", "test1"}) start.WaitWithDefaultTimeout() - Expect(start).To(ExitWithError()) + + wait := podmanTest.Podman([]string{"wait", "test1"}) + wait.WaitWithDefaultTimeout() + Expect(wait).To(ExitWithError()) Eventually(podmanTest.NumberOfContainers(), defaultWaitTimeout, 3.0).Should(BeZero()) }) -- cgit v1.2.3-54-g00ecf From 0b8456b44cfe0fb82e293df227a43070677f15f7 Mon Sep 17 00:00:00 2001 From: zhangguanzhang Date: Wed, 23 Sep 2020 13:21:23 +0800 Subject: apiv2 container limit differ from docker-api Signed-off-by: zhangguanzhang --- pkg/api/handlers/compat/containers.go | 2 +- test/apiv2/20-containers.at | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) (limited to 'pkg') diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 1c2356b92..63c44eaef 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -85,7 +85,7 @@ func ListContainers(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - if _, found := r.URL.Query()["limit"]; found && query.Limit != -1 { + if _, found := r.URL.Query()["limit"]; found && query.Limit > 0 { last := query.Limit if len(containers) > last { containers = containers[len(containers)-last:] diff --git a/test/apiv2/20-containers.at b/test/apiv2/20-containers.at index 9ea3cb7ed..187073fb9 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -176,6 +176,31 @@ t GET containers/$cid/json 200 \ .Config.Cmd='[]' \ .Path="echo" \ .Args[0]="param1" + +# create a running container for after +t POST containers/create '"Image":"'$IMAGE'","Entrypoint":["top"]' 201 \ + .Id~[0-9a-f]\\{64\\} +cid_top=$(jq -r '.Id' <<<"$output") +t GET containers/${cid_top}/json 200 \ + .Config.Entrypoint[0]="top" \ + .Config.Cmd='[]' \ + .Path="top" +t POST containers/${cid_top}/start '' 204 +# make sure the container is running +t GET containers/${cid_top}/json 200 \ + .State.Status="running" + +# 0 means unlimited, need same with docker +t GET containers/json?limit=0 200 \ + .[0].Id~[0-9a-f]\\{64\\} + +t GET 'containers/json?limit=0&all=1' 200 \ + .[0].Id~[0-9a-f]\\{64\\} \ + .[1].Id~[0-9a-f]\\{64\\} + +t POST containers/${cid_top}/stop "" 204 + t DELETE containers/$cid 204 +t DELETE containers/$cid_top 204 # vim: filetype=sh -- cgit v1.2.3-54-g00ecf From e72592c25d5d5b44669fd2975c5f6ec690ce8bfa Mon Sep 17 00:00:00 2001 From: baude Date: Wed, 23 Sep 2020 15:44:23 -0500 Subject: set interactive mode with compat create endpoint when creating a container using the compat endpoint, the interactive bool was being hard set to false and ignoring the user's input. Signed-off-by: baude --- pkg/api/handlers/compat/containers_create.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'pkg') diff --git a/pkg/api/handlers/compat/containers_create.go b/pkg/api/handlers/compat/containers_create.go index 93e4fe540..1d0b4c45d 100644 --- a/pkg/api/handlers/compat/containers_create.go +++ b/pkg/api/handlers/compat/containers_create.go @@ -210,7 +210,7 @@ func makeCreateConfig(ctx context.Context, containerConfig *config.Config, input ImageID: newImage.ID(), BuiltinImgVolumes: nil, // podman ImageVolumeType: "", // podman - Interactive: false, + Interactive: input.OpenStdin, // IpcMode: input.HostConfig.IpcMode, Labels: input.Labels, LogDriver: input.HostConfig.LogConfig.Type, // is this correct -- cgit v1.2.3-54-g00ecf From 5de9b2b5120780c47fcc15e48796c7656563dda7 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Wed, 23 Sep 2020 09:57:38 +0200 Subject: remote load: check if input is directory The remote client does not support loading directories yet. To prevent confusing error messages and to make the behaviour more explicit, check if the input points to a directory and throw an error if needed. Signed-off-by: Valentin Rothberg --- docs/source/markdown/podman-load.1.md | 4 +++- pkg/domain/infra/tunnel/images.go | 7 +++++++ test/e2e/load_test.go | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) (limited to 'pkg') diff --git a/docs/source/markdown/podman-load.1.md b/docs/source/markdown/podman-load.1.md index 917f102f6..308a3493b 100644 --- a/docs/source/markdown/podman-load.1.md +++ b/docs/source/markdown/podman-load.1.md @@ -9,9 +9,11 @@ podman\-load - Load an image from a container image archive into container stora **podman image load** [*options*] [*name*[:*tag*]] ## DESCRIPTION -**podman load** loads an image from either an **oci-archive** or **docker-archive** stored on the local machine into container storage. **podman load** reads from stdin by default or a file if the **input** option is set. +**podman load** loads an image from either an **oci-archive** or a **docker-archive** stored on the local machine into container storage. **podman load** reads from stdin by default or a file if the **input** option is set. You can also specify a name for the image if the archive does not contain a named reference, of if you want an additional name for the local image. +The local client further supports loading an **oci-dir** or a **docker-dir** as created with **podman save** (1). + The **quiet** option suppresses the progress output when set. Note: `:` is a restricted character and cannot be part of the file name. diff --git a/pkg/domain/infra/tunnel/images.go b/pkg/domain/infra/tunnel/images.go index 332a7c2eb..981884109 100644 --- a/pkg/domain/infra/tunnel/images.go +++ b/pkg/domain/infra/tunnel/images.go @@ -199,6 +199,13 @@ func (ir *ImageEngine) Load(ctx context.Context, opts entities.ImageLoadOptions) return nil, err } defer f.Close() + fInfo, err := f.Stat() + if err != nil { + return nil, err + } + if fInfo.IsDir() { + return nil, errors.Errorf("remote client supports archives only but %q is a directory", opts.Input) + } ref := opts.Name if len(opts.Tag) > 0 { ref += ":" + opts.Tag diff --git a/test/e2e/load_test.go b/test/e2e/load_test.go index 2b401a09d..b719b034e 100644 --- a/test/e2e/load_test.go +++ b/test/e2e/load_test.go @@ -139,6 +139,22 @@ var _ = Describe("Podman load", func() { Expect(result.ExitCode()).To(Equal(0)) }) + It("podman-remote load directory", func() { + // Remote-only test looking for the specific remote error + // message when trying to load a directory. + if !IsRemote() { + Skip("Remote only test") + } + + result := podmanTest.Podman([]string{"load", "-i", podmanTest.TempDir}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(125)) + + errMsg := fmt.Sprintf("remote client supports archives only but %q is a directory", podmanTest.TempDir) + found, _ := result.ErrorGrepString(errMsg) + Expect(found).Should(BeTrue()) + }) + It("podman load bogus file", func() { save := podmanTest.PodmanNoCache([]string{"load", "-i", "foobar.tar"}) save.WaitWithDefaultTimeout() -- cgit v1.2.3-54-g00ecf From 8ead007e5ad7f2ec41f5368f68df2acd6f9e08b8 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Wed, 16 Sep 2020 14:30:41 -0700 Subject: Evict containers before removing via V2 API Fixes #7535 Signed-off-by: Jhon Honce --- pkg/api/handlers/compat/containers.go | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) (limited to 'pkg') diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index 63c44eaef..3a904ba87 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -17,6 +17,7 @@ import ( "github.com/docker/go-connections/nat" "github.com/gorilla/schema" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) func RemoveContainer(w http.ResponseWriter, r *http.Request) { @@ -44,8 +45,25 @@ func RemoveContainer(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value("runtime").(*libpod.Runtime) name := utils.GetName(r) con, err := runtime.LookupContainer(name) - if err != nil { - utils.ContainerNotFound(w, name, err) + if err != nil && errors.Cause(err) == define.ErrNoSuchCtr { + // Failed to get container. If force is specified, get the container's ID + // and evict it + if !query.Force { + utils.ContainerNotFound(w, name, err) + return + } + + if _, err := runtime.EvictContainer(r.Context(), name, query.Vols); err != nil { + if errors.Cause(err) == define.ErrNoSuchCtr { + logrus.Debugf("Ignoring error (--allow-missing): %q", err) + w.WriteHeader(http.StatusNoContent) + return + } + logrus.Warn(errors.Wrapf(err, "Failed to evict container: %q", name)) + utils.InternalServerError(w, err) + return + } + w.WriteHeader(http.StatusNoContent) return } -- cgit v1.2.3-54-g00ecf