Repository navigation
Expand file tree
/
Copy pathmain.example.yaml
More file actions
202 lines (195 loc) · 7.55 KB
/
Copy pathmain.example.yaml
File metadata and controls
202 lines (195 loc) · 7.55 KB
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
# yaml-language-server: $schema=https://raw.githubusercontent.com/lorem-dev/doppel/main/doppel-config.schema.json
#
# Doppel reference configuration.
# Copy to main.yaml and adjust; main.yaml is git-ignored.
#
# The line above is what editors read: VS Code's YAML extension and any
# yaml-language-server client will complete field names, show each field's
# description and mark a bad value as you type. Keep it at the top of your own
# main.yaml. The schema is generated from the code -- see
# `doppel config schema` -- and every release also attaches it as an asset for
# anyone who would rather pin a version than follow `main`.
server:
host: "0.0.0.0"
port: 8080
logging:
level: info # trace | debug | info | warn | error
format: json # json | text
control:
socket: /tmp/doppel.sock # `doppel config reload` talks to this socket
templates:
dir: ./templates # uploaded template files are materialized here
admin:
# false runs the proxy with no admin application at all: the port is never
# bound, and `doppel config reload` becomes the only way in. Takes effect on
# restart, not on reload.
enable: true
host: "0.0.0.0"
port: 8081
# Serve the admin API unauthenticated: every action answers as `public`,
# for anyone. Overrides `access`, `groups` and any proxy override, which
# startup then reports as ignored. Off by default.
public: false
# Which names `access` may reference, here and per proxy. "*" is any;
# ["admin", "ci"] allows exactly those. Never governs `public` itself.
# `public` and `admin` are always available whatever the list says.
# [] names nobody, which means the same as `public: true`. Rule V36.
groups: ["*"]
# Serve the browser dashboard from this listener's root. On by default; false
# leaves `/`, `/static/*` and `/robots.txt` unrouted and the JSON API
# untouched. Takes effect on restart.
dashboard: true
# The heading the dashboard shows, and the browser tab's title. Left out here,
# and left out is worth seeing: with no title the header draws the project's
# wordmark, which is the state a first run should show. Set it to a name --
# "billing-api (staging)" -- once several Doppels are open at once.
#
# title: "Doppel"
auth:
header: X-Proxy-Authorization # expects "Bearer {token}"
tokens:
- name: user1
group: admin # admin and user are predefined; custom group names allowed
token: c0a721e2-90ff-40f0-a230-c1ab83d751d8
- name: user2
group: user
token: e14887ef-4025-42d0-81db-a72a2cb3cb76
# public, a single token or group name, or a list of them. Every action
# defaults to the `admin` group, reads included: a proxy document contains
# the `headers` it injects upstream -- see proxy1's `Authorization` below --
# so a public listing publishes those credentials. Set an action to
# `public` only once you are sure nothing under `proxies` is a secret.
access:
list: ["admin", "user"]
read: ["admin", "user"]
create: ["admin"]
update: user1
delete: admin
upload: admin
upload:
limit: 1Mi
proxies:
- name: proxy1
type: http
url: "https://external-service.com/api/v1/"
timeout: 60
resolve:
type: default
access: # per-proxy override; read, update, delete, upload only
read: ["user1", "user2"]
update: user1
headers:
Authorization: "Bearer 1234567890"
loss:
percentage: 0.1 # drop 10 percent of requests
status: 503
latency:
percentage: 0.45 # delay 45 percent of requests
min: 0.05 # seconds
max: 0.2
replace: 1.0 # serve mocks for 100 percent of matching requests
# Rewrite a redirect whose target is under this proxy's base so it points
# back here instead of at the upstream; on by default. Set it to false to
# relay Location byte for byte.
rewrite_redirects: true
body_limit: 1Mi # bounds the body buffered for mocks that extract from it (mock2, mock6 below); default 1Mi
# Order matters. Patterns are matched as unanchored regexes, so a general
# pattern placed first shadows every more specific one below it: with
# `/api/v1/resource/` ahead of `/api/v1/resource/(?P<resource_id>\d+)/`,
# the second can never fire, because the first already matches that path.
# Specific patterns therefore come first here. Doppel does not detect this
# for you -- a shadowed mock is a valid configuration, just a useless one.
mocks:
- name: mock3
request:
method: GET
url: /api/v1/resource/42/
response:
status: 200
json: '{"message": "Success"}'
proxy: # per-mock override of the proxy fault settings
replace: 0.5
latency:
percentage: 0.5
min: 0.1
max: 0.3
- name: mock4
# Referencing an extracted header makes that header required: rendering
# is strict, so a request without X-Trace-Id fails this mock with
# TEMPLATE_RENDER_ERROR rather than rendering an empty string. Use
# `{{ trace_id | default('') }}` below if the header should be optional.
#
# `request_id` is not extracted here and must not be: it is one of the
# system variables Doppel binds itself, always present, minted when the
# client sent no X-Request-ID. Extracting into that name would be read and
# then overwritten, which is what the startup advisory about shadowing is
# for.
request:
method: GET
url: /api/v1/resource/(?P<resource_id>\d+)/
headers:
trace_id: X-Trace-Id
response:
status: 200
json: '{"message": "Success", "id": "{{ resource_id }}", "served_by": "{{ proxy_name }} {{ doppel_version }}"}'
headers:
X-Resource-ID: "{{ resource_id }}"
X-Request-ID: "{{ request_id }}" # a system variable
X-Trace-Id: "{{ trace_id }}"
- name: mock5
request:
method: DELETE
url: /api/v1/resource/(?P<resource_id>\d+)/
response:
status: 204 # 204 forbids a body, so none is declared
headers:
X-Resource-ID: "{{ resource_id }}"
X-Request-ID: "{{ request_id }}"
- name: mock6
request:
method: PUT
url: /api/v1/resource/(?P<resource_id>\d+)/
body:
resource_name: .name
resource_description: .description
resource_items: .content.items
response:
status: 200
template: put.json.j2
headers:
Content-Type: application/json
proxy:
loss:
percentage: 0.0
status: 503
- name: mock1
request:
method: GET
url: /api/v1/resource/
response:
status: 200
body: '{"message": "Success"}'
headers:
Content-Type: application/json
- name: mock2
request:
method: POST
url: /api/v1/resource/
body:
resource_name: .name
resource_description: .description
resource_items: .content.items
query:
filter: .filter
sort: .sort
response:
status: 201
json: '{"message": "Resource created", "name": "{{ resource_name }}", "description": "{{ resource_description }}", "items": {{ resource_items | length }}}'
headers:
Content-Type: application/json
- name: proxy2
type: http
url: "https://other-service.com/"
resolve:
type: header
header: X-Proxy-Name # the header value names the proxy