Home

jsonnetpecker @main - refs - log -
-
https://git.jolheiser.com/jsonnetpecker.git
Woodpecker config service for jsonnet
tree log patch
finish work on extension and add testing vm Signed-off-by: jolheiser <git@jolheiser.com>
Signature
-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgBTEvCQk6VqUAdN2RuH6bj1dNkY oOpbPWj+jw4ua1B1cAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQOMUNtH7pBgacyz+J32ujvG7Epqjhc8W+tn3Ku3zuE11NKwB/ksrNRkYYI0Tp5ku1V 75A0yX3bQPkb8RGsCxVQ4= -----END SSH SIGNATURE-----
jolheiser <git@jolheiser.com>
15 hours ago
7 changed files, 435 additions(+), 7 deletions(-)
README.mddebug.patchflake.lockflake.nixgo.mod.srijsonnet.patchmain.gomain_test.go
M README.md -> README.md
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
diff --git a/README.md b/README.md
index fba182a4529394951195b2055e42e4c9253ea22b..ffb3a3f6b28131828a6e03d253f91c133dcf8e2b 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,28 @@ # jsonnetpecker
 
 A [Woodpecker Config Extension](https://woodpecker-ci.org/docs/usage/extensions/configuration-extension) to parse [jsonnet](https://jsonnet.org/) configs.
 
+## Configuration
+
+Configuration can be provided via flags, environment variables (prefixed with `JSONNETPECKER_`), or a [jsonnet](https://jsonnet.org/) config file (default: `.config.jsonnet`, override with `--config`).
+
+| Flag | Short | Env | Default | Description |
+|------|-------|-----|---------|-------------|
+| `--port` | `-p` | `JSONNETPECKER_PORT` | `0` | Port to listen on |
+| `--public-key` | `-k` | `JSONNETPECKER_PUBLIC_KEY` | | Woodpecker public key for verification |
+| `--public-key-file` | `-K` | `JSONNETPECKER_PUBLIC_KEY_FILE` | | Path to file containing the Woodpecker public key |
+| `--log-level` | `-l` | `JSONNETPECKER_LOG_LEVEL` | `info` | Log level (`debug`, `info`, `warn`, `error`) |
+| `--json` | `-j` | `JSONNETPECKER_JSON` | `false` | Enable JSON logging |
+
+### Config file example
+
+```jsonnet
+{
+  port: 8080,
+  "public-key-file": "/etc/woodpecker/public.key",
+  "log-level": "info",
+}
+```
+
 ## License
 
 [MIT](LICENSE)
I debug.patch
1
2
3
4
diff --git a/debug.patch b/debug.patch
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files /dev/null and b/debug.patch differ
I flake.lock
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000000000000000000000000000000000000..3399eb1da0882c471906638c876f104330f381cc
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,48 @@
+{
+  "nodes": {
+    "gitpecker": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1778680061,
+        "narHash": "sha256-xiYIZtQYAHHZuKWm7kb23ucae4y8JfxgrIPvGCnbpno=",
+        "ref": "refs/heads/main",
+        "rev": "8d7b5b7c99eb32a666999910ff391029cbd790af",
+        "revCount": 4,
+        "type": "git",
+        "url": "https://git.jolheiser.com/gitpecker.git"
+      },
+      "original": {
+        "type": "git",
+        "url": "https://git.jolheiser.com/gitpecker.git"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1778580735,
+        "narHash": "sha256-t+8AVV8ExvOmslz2sLIgw/hJBKlyl65rJvxjvvjHgpE=",
+        "owner": "nixos",
+        "repo": "nixpkgs",
+        "rev": "48d91f2c0ce7b9e589f967d4f685153dd765dcdd",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nixos",
+        "ref": "nixpkgs-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "gitpecker": "gitpecker",
+        "nixpkgs": "nixpkgs"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
I flake.nix
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000000000000000000000000000000000000..fa85ec12592da2f3a6df8125d6865b737aad0c45
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,246 @@
+{
+  description = "jsonnet config extension for woodpecker";
+  inputs = {
+    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
+    gitpecker = {
+      url = "git+https://git.jolheiser.com/gitpecker.git";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+  };
+  outputs =
+    {
+      self,
+      nixpkgs,
+      gitpecker,
+      ...
+    }:
+    let
+      systems = [
+        "aarch64-darwin"
+        "aarch64-linux"
+        "x86_64-darwin"
+        "x86_64-linux"
+      ];
+      forAllSystems = f: nixpkgs.lib.genAttrs systems f;
+    in
+    {
+      packages = forAllSystems (
+        system:
+        let
+          pkgs = import nixpkgs { inherit system; };
+        in
+        {
+          default = self.packages.${system}.jsonnetpecker;
+          jsonnetpecker = pkgs.buildGoModule rec {
+            pname = "jsonnetpecker";
+            version = self.rev or "dev";
+            src = pkgs.nix-gitignore.gitignoreSource [ ] (
+              builtins.path {
+                name = pname;
+                path = ./.;
+              }
+            );
+            vendorHash = nixpkgs.lib.fileContents ./go.mod.sri;
+            meta = {
+              description = "jsonnet config extension for woodpecker";
+              homepage = "https://git.jolheiser.com/jsonnetpecker";
+              mainProgram = "jsonnetpecker";
+            };
+          };
+        }
+      );
+      devShells = forAllSystems (
+        system:
+        let
+          pkgs = import nixpkgs { inherit system; };
+        in
+        {
+          default = pkgs.mkShell {
+            nativeBuildInputs = with pkgs; [
+              go
+              gopls
+              gofumpt
+              woodpecker-server
+            ];
+          };
+        }
+      );
+      nixosConfigurations.jsonnetpeckerVM = nixpkgs.lib.nixosSystem {
+        system = "x86_64-linux";
+        modules = [
+          (
+            { pkgs, ... }:
+            {
+              services.getty.autologinUser = "root";
+              services.dex = {
+                enable = true;
+                settings = {
+                  issuer = "http://localhost:5556/dex";
+                  storage.type = "memory";
+                  web.http = "0.0.0.0:5556";
+                  connectors = [
+                    {
+                      type = "mockCallback";
+                      id = "mock";
+                      name = "Mock";
+                    }
+                  ];
+                  oauth2.skipApprovalScreen = true;
+                  staticClients = [
+                    {
+                      id = "woodpecker";
+                      secret = "woodpecker-secret";
+                      name = "Woodpecker";
+                      redirectURIs = [ "http://localhost:8000/authorize" ];
+                    }
+                  ];
+                };
+              };
+              services.woodpecker-server = {
+                enable = true;
+                package = pkgs.woodpecker-server.overrideAttrs (oldAttrs: {
+                  doCheck = false;
+                  patches = (oldAttrs.patches or [ ]) ++ [
+                    ./jsonnet.patch
+                    ./debug.patch
+                  ];
+                });
+                environment = {
+                  WOODPECKER_HOST = "http://0.0.0.0:8000";
+                  WOODPECKER_OPEN = "true";
+                  WOODPECKER_ADDON_FORGE = "${gitpecker.packages.x86_64-linux.gitpecker}/bin/gitpecker";
+                  WOODPECKER_CONFIG_SERVICE_ENDPOINT = "http://localhost:8080";
+                  WOODPECKER_EXTENSIONS_ALLOWED_HOSTS = "localhost";
+                  WOODPECKER_LOG_LEVEL = "trace";
+                  WOODPECKER_ADMIN = "kilgore@kilgore.trout";
+                  WOODPECKER_AGENT_SECRET = "Testing123";
+                  WOODPECKER_EXTRA_DEFAULT_PIPELINE_CONFIGS = ".woodpecker.jsonnet";
+                  GITPECKER_REPOS = "/var/lib/forge/";
+                  GITPECKER_URL = "/var/lib/forge/";
+                  GITPECKER_PROVIDER = "http://localhost:5556/dex";
+                  GITPECKER_CLIENT_ID = "woodpecker";
+                  GITPECKER_CLIENT_SECRET = "woodpecker-secret";
+                  GITPECKER_REDIRECT = "http://localhost:8000/authorize";
+                };
+              };
+              systemd.services.woodpecker-server.after = [ "dex.service" ];
+              services.woodpecker-agents.agents."007" = {
+                enable = true;
+                path = with pkgs; [
+                  git
+                  git-lfs
+                  bash
+                  coreutils
+                  woodpecker-plugin-git
+                ];
+                environment = {
+                  WOODPECKER_AGENT_SECRET = "Testing123";
+                };
+              };
+              systemd.services.jsonnetpecker = {
+                description = "jsonnetpecker config extension";
+                wantedBy = [ "multi-user.target" ];
+                after = [
+                  "woodpecker-server.service"
+                  "network.target"
+                ];
+                path = with pkgs; [ curl ];
+                serviceConfig = {
+                  Type = "simple";
+                  RuntimeDirectory = "jsonnetpecker";
+                  ExecStartPre = pkgs.writeShellScript "jsonnetpecker-fetch-pubkey" ''
+                    until curl -sf http://localhost:8000/api/healthz > /dev/null 2>&1; do sleep 1; done
+                    curl -sf "http://localhost:8000/api/signature/public-key" -o /run/jsonnetpecker/pubkey.pem
+                  '';
+                  ExecStart = "${self.packages.x86_64-linux.jsonnetpecker}/bin/jsonnetpecker --port 8080 --log-level debug --public-key-file /run/jsonnetpecker/pubkey.pem";
+                };
+              };
+              virtualisation.vmVariant.virtualisation = {
+                cores = 2;
+                memorySize = 2048;
+                graphics = false;
+                forwardPorts = [
+                  {
+                    from = "host";
+                    host.port = 8000;
+                    guest.port = 8000;
+                  }
+                  {
+                    from = "host";
+                    host.port = 5556;
+                    guest.port = 5556;
+                  }
+                ];
+              };
+              networking.firewall.enable = false;
+              systemd.services."setup-vm" = {
+                wantedBy = [ "multi-user.target" ];
+                path = with pkgs; [
+                  git
+                ];
+                serviceConfig = {
+                  Type = "oneshot";
+                  RemainAfterExit = true;
+                  User = "root";
+                  Group = "root";
+                  ExecStart =
+                    let
+                      pipeline = pkgs.writeText "pipeline.jsonnet" ''
+                        local step(name) = {
+                          name: name,
+                          image: "bash",
+                          commands: ['echo "' + name + '"'],
+                        };
+
+                        {
+                          // when: {event: "manual"},
+                          steps: [step("honk"), step("bonk")],
+                        }
+                      '';
+                    in
+                    pkgs.writeShellScript "setup-vm-script" ''
+                      git config --global user.name "NixUser"
+                      git config --global user.email "nixuser@example.com"
+                      git config --global init.defaultBranch main
+                      git config --global push.autoSetupRemote true
+
+                      mkdir -p /var/lib/forge
+                      pushd /var/lib/forge
+                      git init --bare test1.git
+                      popd
+                      git clone /var/lib/forge/test1.git
+                      pushd test1
+                      cp ${pipeline} .woodpecker.jsonnet
+                      git add .
+                      git commit -m "honk"
+                      git push
+                      popd
+                    '';
+                };
+              };
+              environment.systemPackages = with pkgs; [
+                git
+              ];
+              system.stateVersion = "23.11";
+            }
+          )
+        ];
+      };
+      apps = forAllSystems (
+        system:
+        let
+          pkgs = import nixpkgs { inherit system; };
+        in
+        {
+          vm = {
+            type = "app";
+            program = "${pkgs.writeShellScript "vm" ''
+              nixos-rebuild build-vm --flake .#jsonnetpeckerVM
+              QEMU_NET_OPTS="restrict=off" ./result/bin/run-nixos-vm
+              rm nixos.qcow2
+            ''}";
+          };
+        }
+      );
+    };
+}
I go.mod.sri
1
2
3
4
5
6
7
8
diff --git a/go.mod.sri b/go.mod.sri
new file mode 100644
index 0000000000000000000000000000000000000000..ed9babc7712fefcaf638ad4a546e0aa117263f5f
--- /dev/null
+++ b/go.mod.sri
@@ -0,0 +1 @@
+sha256-kRc/W1YqcqOVdwgJKSQCwQlAx2Pr5nw5q6HZlSik1S0=
\ No newline at end of file
I jsonnet.patch
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
diff --git a/jsonnet.patch b/jsonnet.patch
new file mode 100644
index 0000000000000000000000000000000000000000..a22c9a39e53081cfc844bf971a5722c6f76c4df5
--- /dev/null
+++ b/jsonnet.patch
@@ -0,0 +1,88 @@
+From 6d284379349794ad8939842e4067828aaab86cb9 Mon Sep 17 00:00:00 2001
+From: jolheiser <git@jolheiser.com>
+Date: Wed, 13 May 2026 15:14:24 -0500
+Subject: [PATCH] feat: allow admins to extend default pipeline config paths
+
+Signed-off-by: jolheiser <git@jolheiser.com>
+---
+ cmd/server/flags.go             | 8 ++++++++
+ cmd/server/setup.go             | 4 ++++
+ server/services/config/forge.go | 2 +-
+ shared/constant/constant.go     | 4 ++--
+ 4 files changed, 15 insertions(+), 3 deletions(-)
+
+diff --git a/cmd/server/flags.go b/cmd/server/flags.go
+index 2638bf5f7..ecfdb5d5f 100644
+--- a/cmd/server/flags.go
++++ b/cmd/server/flags.go
+@@ -289,6 +289,14 @@ var flags = append([]cli.Flag{
+ 		Name:    "config-extension-netrc",
+ 		Usage:   "whether global configuration extension should receive netrc data",
+ 	},
++	&cli.StringSliceFlag{
++		Sources: cli.EnvVars("WOODPECKER_EXTRA_DEFAULT_PIPELINE_CONFIGS"),
++		Name:    "extra-default-pipeline-configs",
++		Usage:   "extra default pipeline config paths to check",
++		Config: cli.StringConfig{
++			TrimSpace: true,
++		},
++	},
+ 	&cli.StringFlag{
+ 		Sources: cli.EnvVars("WOODPECKER_REGISTRY_EXTENSION_ENDPOINT"),
+ 		Name:    "registry-extension-endpoint",
+diff --git a/cmd/server/setup.go b/cmd/server/setup.go
+index 6e8bb194c..5e74fbaf4 100644
+--- a/cmd/server/setup.go
++++ b/cmd/server/setup.go
+@@ -45,6 +45,7 @@ import (
+ 	"go.woodpecker-ci.org/woodpecker/v3/server/store"
+ 	"go.woodpecker-ci.org/woodpecker/v3/server/store/datastore"
+ 	"go.woodpecker-ci.org/woodpecker/v3/server/store/types"
++	"go.woodpecker-ci.org/woodpecker/v3/shared/constant"
+ )
+ 
+ const (
+@@ -158,6 +159,9 @@ func setupJWTSecret(_store store.Store) (string, error) {
+ }
+ 
+ func setupEvilGlobals(ctx context.Context, c *cli.Command, s store.Store) (err error) {
++	// default config paths
++	constant.DefaultConfigOrder = append(constant.DefaultConfigOrder, c.StringSlice("extra-default-pipeline-configs")...)
++
+ 	// services
+ 	server.Config.Services.Logs = logging.New()
+ 	server.Config.Services.Membership = setupMembershipService(ctx, s)
+diff --git a/server/services/config/forge.go b/server/services/config/forge.go
+index 57bf040e8..aee3ad7c2 100644
+--- a/server/services/config/forge.go
++++ b/server/services/config/forge.go
+@@ -97,7 +97,7 @@ func (f *forgeFetcherContext) fetch(c context.Context, config string) ([]*types.
+ 
+ 	log.Trace().Msgf("configFetcher[%s]: user did not define own config, following default procedure", f.repo.FullName)
+ 	// for the order see shared/constants/constants.go
+-	fileMetas, err := f.getFirstAvailableConfig(ctx, constant.DefaultConfigOrder[:])
++	fileMetas, err := f.getFirstAvailableConfig(ctx, constant.DefaultConfigOrder)
+ 	if err == nil {
+ 		return fileMetas, nil
+ 	}
+diff --git a/shared/constant/constant.go b/shared/constant/constant.go
+index e56957b82..9de20ca53 100644
+--- a/shared/constant/constant.go
++++ b/shared/constant/constant.go
+@@ -16,9 +16,9 @@ package constant
+ 
+ import "time"
+ 
+-// DefaultConfigOrder represent the priority in witch woodpecker search for a pipeline config by default
++// DefaultConfigOrder represent the priority in which woodpecker searches for a pipeline config by default
+ // folders are indicated by supplying a trailing slash.
+-var DefaultConfigOrder = [...]string{
++var DefaultConfigOrder = []string{
+ 	".woodpecker/",
+ 	".woodpecker.yaml",
+ 	".woodpecker.yml",
+
+base-commit: 48e1ece20057e59de4e8e3fe25fc1f3a41e8a020
+-- 
+2.50.1
+
M main.go -> main.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
diff --git a/main.go b/main.go
index 8b22f43e0407c32e4b9fc2f0e2eceec185f748f2..98fa14b4f3f18e8bac1f88d7be582f24a861941a 100644
--- a/main.go
+++ b/main.go
@@ -9,6 +9,7 @@ 	"encoding/pem"
 	"errors"
 	"flag"
 	"fmt"
+	"io"
 	"log/slog"
 	"net/http"
 	"os"
@@ -32,7 +33,7 @@ 	logLevel slog.Level
 	logJSON  bool
 }
 
-const magicKey = "woodpcecker-ci-extensions"
+const magicKey = "woodpecker-ci-extensions"
 
 func maine() error {
 	var args args
@@ -71,6 +72,14 @@ 		ff.WithEnvVarPrefix("JSONNETPECKER"),
 	); err != nil {
 		return err
 	}
+	logOpts := &slog.HandlerOptions{Level: args.logLevel}
+	var logger slog.Handler
+	if args.logJSON {
+		logger = slog.NewJSONHandler(os.Stderr, logOpts)
+	} else {
+		logger = slog.NewTextHandler(os.Stderr, logOpts)
+	}
+	slog.SetDefault(slog.New(logger))
 
 	if args.pubKeyFile != "" {
 		data, err := os.ReadFile(args.pubKeyFile)
@@ -100,8 +109,15 @@ 	mux.Get("/", func(w http.ResponseWriter, r *http.Request) {
 		w.Write([]byte(`send me some jsonnet`))
 	})
 	mux.With(verifyMiddleware(pubKey)).Post("/", func(w http.ResponseWriter, r *http.Request) {
+		body, err := io.ReadAll(r.Body)
+		if err != nil {
+			json.NewEncoder(w).Encode(map[string]string{"error": "could not read body"})
+			w.WriteHeader(http.StatusBadRequest)
+			return
+		}
+		slog.Debug("incoming request", slog.String("body", string(body)))
 		var req Request
-		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
+		if err := json.Unmarshal(body, &req); err != nil {
 			w.WriteHeader(http.StatusBadRequest)
 			json.NewEncoder(w).Encode(map[string]string{"error": "could not decode JSON"})
 			return
@@ -109,6 +125,7 @@ 		}
 
 		configs := make([]Config, 0, len(req.Configs))
 		for _, cfg := range req.Configs {
+			slog.Debug("incoming config", slog.String("name", cfg.Name), slog.String("data", cfg.Data))
 			if strings.HasSuffix(cfg.Name, "jsonnet") {
 				vm := jsonnet.MakeVM()
 				jsonData, err := vm.EvaluateAnonymousSnippet(cfg.Name, cfg.Data)
@@ -130,7 +147,8 @@ 				cfg.Data = string(yamlData)
 			}
 			configs = append(configs, cfg)
 		}
-		json.NewEncoder(w).Encode(Request{Configs: configs})
+		slog.Debug("YAML response", slog.Any("configs", configs))
+		json.NewEncoder(w).Encode(Response{Configs: configs})
 	})
 
 	return mux
@@ -173,6 +191,7 @@ func verifyMiddleware(pubKey ed25519.PublicKey) func(http.Handler) http.Handler {
 	return func(next http.Handler) http.Handler {
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			if err := verify(pubKey, r); err != nil {
+				slog.Error("verify failed", slog.Any("err", err), slog.String("signature", r.Header.Get("Signature")), slog.String("signature-input", r.Header.Get("Signature-Input")), slog.String("content-digest", r.Header.Get("Content-Digest")))
 				w.WriteHeader(http.StatusUnauthorized)
 				json.NewEncoder(w).Encode(map[string]string{"error": "Failed to verify request"})
 				return
@@ -183,6 +202,10 @@ 	}
 }
 
 type Request struct {
+	Configs []Config `json:"configuration"`
+}
+
+type Response struct {
 	Configs []Config `json:"configs"`
 }
 
M main_test.go -> main_test.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
diff --git a/main_test.go b/main_test.go
index 1e563ffd4089ee6081746c6b315e3f52ec568214..bf56fa6c655f613d14d7ea4701a4736f7a484fdd 100644
--- a/main_test.go
+++ b/main_test.go
@@ -182,7 +182,7 @@ 	handler.ServeHTTP(w, req)
 
 	assert.Equal(t, http.StatusOK, w.Code)
 
-	var resp Request
+	var resp Response
 	err = json.NewDecoder(w.Body).Decode(&resp)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, len(resp.Configs))
@@ -214,7 +214,7 @@ 	handler.ServeHTTP(w, req)
 
 	assert.Equal(t, http.StatusOK, w.Code)
 
-	var resp Request
+	var resp Response
 	err = json.NewDecoder(w.Body).Decode(&resp)
 	assert.NoError(t, err)
 	assert.Equal(t, 1, len(resp.Configs))
@@ -255,7 +255,7 @@ 	handler.ServeHTTP(w, req)
 
 	assert.Equal(t, http.StatusOK, w.Code)
 
-	var resp Request
+	var resp Response
 	err = json.NewDecoder(w.Body).Decode(&resp)
 	assert.NoError(t, err)
 	assert.Equal(t, 3, len(resp.Configs))
@@ -302,7 +302,7 @@ 	handler.ServeHTTP(w, req)
 
 	assert.Equal(t, http.StatusOK, w.Code)
 
-	var resp Request
+	var resp Response
 	err = json.NewDecoder(w.Body).Decode(&resp)
 	assert.NoError(t, err)
 	assert.Equal(t, 2, len(resp.Configs))