├── .devcontainer
└── devcontainer.json
├── .dockerignore
├── .env
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── app.env
├── build
└── node
│ ├── Dockerfile
│ └── entrypoint.sh
├── docker-compose-volumes.yml
├── docker-compose.yml
└── src
└── backstage
├── README.md
├── app-config.local.yaml
├── app-config.production.yaml
├── app-config.yaml
├── backstage.json
├── catalog-info.yaml
├── examples
├── entities.yaml
├── org.yaml
└── template
│ ├── content
│ ├── catalog-info.yaml
│ ├── index.js
│ └── package.json
│ └── template.yaml
├── lerna.json
├── package.json
├── packages
├── README.md
├── app
│ ├── .eslintrc.js
│ ├── cypress.json
│ ├── cypress
│ │ ├── .eslintrc.json
│ │ └── integration
│ │ │ └── app.js
│ ├── package.json
│ ├── public
│ │ ├── android-chrome-192x192.png
│ │ ├── apple-touch-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── manifest.json
│ │ ├── robots.txt
│ │ └── safari-pinned-tab.svg
│ └── src
│ │ ├── App.test.tsx
│ │ ├── App.tsx
│ │ ├── apis.ts
│ │ ├── components
│ │ ├── Root
│ │ │ ├── LogoFull.tsx
│ │ │ ├── LogoIcon.tsx
│ │ │ ├── Root.tsx
│ │ │ └── index.ts
│ │ ├── catalog
│ │ │ └── EntityPage.tsx
│ │ └── search
│ │ │ └── SearchPage.tsx
│ │ ├── index.tsx
│ │ └── setupTests.ts
└── backend
│ ├── .eslintrc.js
│ ├── Dockerfile
│ ├── README.md
│ ├── package.json
│ └── src
│ ├── index.test.ts
│ ├── index.ts
│ ├── plugins
│ ├── app.ts
│ ├── auth.ts
│ ├── catalog.ts
│ ├── proxy.ts
│ ├── scaffolder.ts
│ ├── search.ts
│ └── techdocs.ts
│ └── types.ts
├── plugins
└── README.md
├── tsconfig.json
└── yarn.lock
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Backstage",
3 | "dockerComposeFile": [
4 | "../docker-compose.yml",
5 | "../docker-compose-volumes.yml"
6 | ],
7 | "service": "cli",
8 | "workspaceFolder": "${localWorkspaceFolder}",
9 | "shutdownAction": "stopCompose",
10 | "userEnvProbe": "loginInteractiveShell",
11 | "remoteUser": "node",
12 | // "postCreateCommand": "sudo chown node src/backstage/node_modules",
13 | "runServices": ["cli"],
14 | "customizations": {
15 | "vscode": {
16 | "extensions": [
17 | "dbaeumer.vscode-eslint",
18 | "esbenp.prettier-vscode",
19 | "ms-vsliveshare.vsliveshare",
20 | "eamodio.gitlens"
21 | ]
22 | }
23 | },
24 | // This is useful to have docker and docker-compose working inside the container
25 | // Ref: https://github.com/devcontainers/features/tree/main/src/docker-from-docker
26 | "features": {
27 | "ghcr.io/devcontainers/features/docker-from-docker:1": {
28 | "dockerDashComposeVersion": "v2"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | .yarn/cache
3 | .yarn/install-state.gz
4 | *.local.yaml
5 | .cache
6 | .devcontainers
7 |
8 | # Backstage specific.
9 | */node_modules
10 | */packages/*/dist
11 | */packages/*/node_modules
12 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | # Uncomment this to use a docker volumes for node_modules.
2 | # IT IS REQUIRED when working with devcontainers.
3 | # COMPOSE_FILE=docker-compose.yml:docker-compose-volumes.yml
4 | COMPOSE_PROFILES=app
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # macOS
2 | .DS_Store
3 |
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 |
12 | # Coverage directory generated when running tests with coverage
13 | coverage
14 |
15 | # Dependencies
16 | node_modules/
17 |
18 | # Yarn 3 files
19 | .pnp.*
20 | .yarn/*
21 | !.yarn/patches
22 | !.yarn/plugins
23 | !.yarn/releases
24 | !.yarn/sdks
25 | !.yarn/versions
26 |
27 | # Node version directives
28 | .nvmrc
29 |
30 | # dotenv environment variables file
31 | .env
32 | .env.test
33 |
34 | # Build output
35 | dist
36 | dist-types
37 |
38 | # Temporary change files created by Vim
39 | *.swp
40 |
41 | # MkDocs build output
42 | site
43 |
44 | # Sensitive credentials
45 | *-credentials.yaml
46 |
47 | .vscode/
48 |
49 | # vscode database functionality support files
50 | *.session.sql
51 |
52 | # Keep empty directories with gitkeep files.
53 | .cache/**
54 | !.cache/**/
55 | !.cache/.gitkeep
56 | !.cache/**/.gitkeep
57 | database/**
58 | !database/**/
59 | !database/.gitkeep
60 | !database/**/.gitkeep
61 |
62 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | cli:
2 | @docker-compose run --rm -u root cli chown -R node:node .cache
3 | @docker-compose run --rm cli bash
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Dockerized Backstage.io with Devcontainers
2 |
3 | This is a PoC repository to run Backstage.io with Docker and Devcontainers.
4 |
5 | The main goal here is to demonstrate how to use Development Containers in a
6 | real-world scenario but leaving open the door to use a standard dockerized workflow.
7 | Another advantage of this approach is **performance**; Docker on macOS suffers of
8 | filesystem performance issues, to fix that:
9 |
10 | 1. Code `src/backstage` is bind mounted to `/usr/src/app`
11 | 1. **OPTIONAL**: Node modules are mounted with a named volume on `src/backstage/node\_modules`
12 |
13 | ## How is implemented
14 |
15 | Let's start by commenting `devcontainer.json`:
16 |
17 | ```json
18 | {
19 | "name": "Backstage",
20 |
21 | // When running inside devcontainers we want to use named volumes for node_modules.
22 | "dockerComposeFile": [
23 | "../docker-compose.yml",
24 | "../docker-compose-volumes.yml"
25 | ],
26 |
27 | // Which docker-compose service to use for the devcontainer instance.
28 | "service": "cli",
29 |
30 | // https://containers.dev/implementors/json_reference/#variables-in-devcontainerjson
31 | "workspaceFolder": "${localWorkspaceFolder}",
32 | "shutdownAction": "stopCompose",
33 | "userEnvProbe": "loginInteractiveShell",
34 |
35 | // Run it as a non-root user.
36 | "remoteUser": "node",
37 |
38 | // Just run the cli container, devcontainer does not respect COMPOSE_PROFILES
39 | "runServices": ["cli"],
40 |
41 | // Add some vscode extensions.
42 | "customizations": {
43 | "vscode": {
44 | "extensions": [
45 | "dbaeumer.vscode-eslint",
46 | "esbenp.prettier-vscode",
47 | "ms-vsliveshare.vsliveshare",
48 | "eamodio.gitlens"
49 | ]
50 | }
51 | },
52 |
53 | // This is useful to have docker and docker-compose working inside the container
54 | // Ref: https://github.com/devcontainers/features/tree/main/src/docker-from-docker
55 | "features": {
56 | "ghcr.io/devcontainers/features/docker-from-docker:1": {
57 | "dockerDashComposeVersion": "v2"
58 | }
59 | }
60 | }
61 | ```
62 |
63 | And now the docker compose files:
64 |
65 | ```yaml
66 | # docker-compose.yml
67 |
68 | version: "3"
69 | services:
70 | # This is the development container, can be used
71 | # with and without devcontainers.
72 | cli:
73 | build:
74 | context: .
75 | dockerfile: build/node/Dockerfile
76 | target: cli
77 | # We use another profile for the cli, to exclude it from running,
78 | # with a docker-compose up
79 | profiles: ["cli"]
80 | env_file:
81 | - app.env
82 | volumes:
83 | # This is used to have the same local path, inside the container.
84 | - $PWD:$PWD
85 |
86 | # The following volumes are here to keep npm/yarn caching and
87 | # bash history.
88 | - ./.cache/yarn:/usr/local/share/.cache/yarn
89 | - ./.cache/npm:/root/.npm
90 | - ./.cache/dotfiles:/dotfiles
91 | # Non root.
92 | user: node
93 | hostname: cli
94 |
95 | # Start in the same local root.
96 | working_dir: $PWD
97 |
98 | # Keep it always running, this is needed by devcontainers.
99 | command: /bin/sh -c "while sleep 1000; do :; done"
100 |
101 | # This is the container to just run backstage.
102 | app:
103 | build:
104 | context: .
105 | dockerfile: build/node/Dockerfile
106 | target: run
107 | env_file:
108 | - app.env
109 | volumes:
110 | - ./src/backstage/:/usr/src/app
111 | - ./.cache/yarn:/usr/local/share/.cache/yarn
112 | - ./.cache/npm:/root/.npm
113 |
114 | # This is the app profile, used to spin up the services with
115 | # a docker-compose up
116 | profiles: ["app"]
117 | ports:
118 | - 3000:3000
119 | - 7007:7007
120 | user: node
121 | depends_on:
122 | - db
123 |
124 | db:
125 | image: postgres:13.3
126 | volumes:
127 | - ./database:/usr/src/app
128 | profiles: ["app"]
129 | env_file:
130 | - app.env
131 |
132 | ---
133 | # docker-compose-volumes.yml
134 |
135 | version: "3"
136 | services:
137 | cli:
138 | # Adding the named volume on top of the bind mount.
139 | # NOTE: The volume here must be mounted under the full path of the
140 | # backstage source code.
141 | volumes:
142 | # @TODO - Hardcoded src/backstage path must be a variables.
143 | - node_modules:$PWD/src/backstage/node_modules
144 | app:
145 | # Adding the named volume on top of the bind mount.
146 | volumes:
147 | - node_modules:/usr/src/app/node_modules
148 | # Node modules volume.
149 | volumes:
150 | node_modules:
151 | ```
152 |
153 | Using or not the named volume must be controlled with `.env` variables:
154 |
155 | ```shell
156 | # Uncomment this to use a docker volumes for node_modules.
157 | # IT IS REQUIRED when working with devcontainers.
158 |
159 | # COMPOSE_FILE=docker-compose.yml:docker-compose-volumes.yml
160 | COMPOSE_PROFILES=app
161 | ```
162 |
163 | ## Run it
164 |
165 | ### All bind mounted
166 |
167 | Just keep `.env`:
168 |
169 | ```shell
170 | # COMPOSE_FILE=docker-compose.yml:docker-compose-volumes.yml
171 | COMPOSE_PROFILES=app
172 | ```
173 |
174 | And run:
175 |
176 | ```shell
177 | > docker-compose up -d
178 | > make cli
179 | ```
180 |
181 | ### Local: Bind mount + named volume
182 |
183 | Change `.env` to:
184 |
185 | ```shell
186 | COMPOSE_FILE=docker-compose.yml:docker-compose-volumes.yml
187 | COMPOSE_PROFILES=app
188 | ```
189 |
190 | And run:
191 |
192 | ```shell
193 | > docker-compose up -d
194 | > make cli
195 | ```
196 |
197 | ### Devcontainers: Bind mount + named volume
198 |
199 | Change `.env` to:
200 |
201 | ```shell
202 | COMPOSE_FILE=docker-compose.yml:docker-compose-volumes.yml
203 | COMPOSE_PROFILES=app
204 | ```
205 |
206 | Now open the VSCode command palette and run:
207 | `Dev Containers: Rebuild and Reopen in Containers`
208 |
209 | At this point you'll have a working instance of VSCode
210 | configured with the devcontainers, just open a terminal and run:
211 |
212 | ```shell
213 | > docker-compose up -d
214 | ```
215 |
216 | Yes, thanks to the extension installed, we can use docker as
217 | we do in the host.
218 |
--------------------------------------------------------------------------------
/app.env:
--------------------------------------------------------------------------------
1 | POSTGRES_HOST=db
2 | POSTGRES_PORT=5432
3 | POSTGRES_USER=backstage
4 | POSTGRES_PASSWORD=backstage
5 | POSTGRES_DB=backstage
6 |
--------------------------------------------------------------------------------
/build/node/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16-bullseye-slim as cli
2 |
3 | WORKDIR /usr/src/app
4 |
5 | # Install some useful cli tools.
6 | RUN apt-get update && apt-get install -y \
7 | gettext-base git libsqlite3-dev python3 cmake g++ git vim curl locales sudo
8 |
9 | # Add node user to sudoers.
10 | # Refs: https://code.visualstudio.com/remote/advancedcontainers/add-nonroot-user#_creating-a-nonroot-user
11 | RUN echo node ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/node \
12 | && chmod 0440 /etc/sudoers.d/node
13 |
14 | # Configure locale.
15 | ENV LANG en_US.UTF-8
16 | ENV LANGUAGE en_US:en
17 | ENV LC_ALL en_US.UTF-8
18 |
19 | # Install oh-my-bash and configure locale.
20 | ARG USERNAME=node
21 | RUN su -c bash -c "$(curl -fsSL https://raw.githubusercontent.com/ohmybash/oh-my-bash/master/tools/install.sh)" $USERNAME && \
22 | sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && \
23 | locale-gen
24 |
25 | # Set custom aliases.
26 | RUN echo "alias dc=docker-compose" >> /etc/bash.bashrc
27 |
28 | # We just need this to avoid to create an empty directory
29 | # as root:root when initialized for the first time.
30 | # See https://serverfault.com/a/984599 - https://docs.docker.com/storage/volumes/
31 | # @TODO - Hardcoded src/backstage path must be a variables.
32 | RUN mkdir -p src/backstage/node_modules \
33 | && chown -R $USERNAME:$USERNAME src/backstage/node_modules
34 |
35 | # Set a custom HISTFILE env to keep history where we want.
36 | ENV HISTFILE /dotfiles/.bash_history
37 |
38 | FROM node:16-bullseye-slim as run
39 |
40 | ARG USERNAME=node
41 | WORKDIR /usr/src/app
42 |
43 | # We just need this to avoid to create an empty directory
44 | # as root:root when initialized for the first time.
45 | # See https://serverfault.com/a/984599 - https://docs.docker.com/storage/volumes/
46 | RUN mkdir -p node_modules \
47 | && chown -R $USERNAME:$USERNAME node_modules
48 |
49 | # Configure the entrypoint script.
50 | COPY build/node/entrypoint.sh /entrypoint.sh
51 | RUN chmod +x /entrypoint.sh
52 |
53 | EXPOSE 3000 7007
54 |
55 | ENTRYPOINT ["/entrypoint.sh"]
56 | CMD ["yarn", "dev"]
57 |
--------------------------------------------------------------------------------
/build/node/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eo pipefail
3 | shopt -s nullglob
4 | BASE=${PWD}
5 |
6 | if [ "${1}" = 'yarn' ]; then
7 | echo "Installing yarn libraries..."
8 | cd ${BASE} && yarn install --frozen-lockfile
9 | fi
10 |
11 | exec "$@"
12 |
--------------------------------------------------------------------------------
/docker-compose-volumes.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | cli:
4 | volumes:
5 | # @TODO - Hardcoded src/backstage path must be a variables.
6 | - node_modules:$PWD/src/backstage/node_modules
7 | app:
8 | volumes:
9 | - node_modules:/usr/src/app/node_modules
10 | volumes:
11 | node_modules:
12 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 | services:
3 | cli:
4 | build:
5 | context: .
6 | dockerfile: build/node/Dockerfile
7 | target: cli
8 | profiles: ["cli"]
9 | env_file:
10 | - app.env
11 | volumes:
12 | - $PWD:$PWD
13 | - ./.cache/yarn:/usr/local/share/.cache/yarn
14 | - ./.cache/npm:/root/.npm
15 | - ./.cache/dotfiles:/dotfiles
16 | user: node
17 | hostname: cli
18 | working_dir: $PWD
19 | command: /bin/sh -c "while sleep 1000; do :; done"
20 |
21 | app:
22 | build:
23 | context: .
24 | dockerfile: build/node/Dockerfile
25 | target: run
26 | env_file:
27 | - app.env
28 | volumes:
29 | - ./src/backstage/:/usr/src/app
30 | - ./.cache/yarn:/usr/local/share/.cache/yarn
31 | - ./.cache/npm:/root/.npm
32 | profiles: ["app"]
33 | ports:
34 | - 3000:3000
35 | - 7007:7007
36 | user: node
37 | depends_on:
38 | - db
39 |
40 | db:
41 | image: postgres:13.3
42 | volumes:
43 | - ./database:/usr/src/app
44 | profiles: ["app"]
45 | env_file:
46 | - app.env
47 |
--------------------------------------------------------------------------------
/src/backstage/README.md:
--------------------------------------------------------------------------------
1 | # [Backstage](https://backstage.io)
2 |
3 | This is your newly scaffolded Backstage App, Good Luck!
4 |
5 | To start the app, run:
6 |
7 | ```sh
8 | yarn install
9 | yarn dev
10 | ```
11 |
--------------------------------------------------------------------------------
/src/backstage/app-config.local.yaml:
--------------------------------------------------------------------------------
1 | app:
2 | title: Scaffolded Backstage App
3 | baseUrl: http://0.0.0.0:3000
4 | backend:
5 | baseUrl: http://127.0.0.1:7007
6 | listen:
7 | port: 7007
8 | csp:
9 | connect-src: ["'self'", 'http:', 'https:']
10 | cors:
11 | origin: http://127.0.0.1:3000
12 | methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
13 | credentials: true
14 | database:
15 | client: pg
16 | connection:
17 | host: ${POSTGRES_HOST}
18 | port: ${POSTGRES_PORT}
19 | user: ${POSTGRES_USER}
20 | password: ${POSTGRES_PASSWORD}
21 | cache:
22 | store: memory
23 |
--------------------------------------------------------------------------------
/src/backstage/app-config.production.yaml:
--------------------------------------------------------------------------------
1 | app:
2 | # Should be the same as backend.baseUrl when using the `app-backend` plugin.
3 | baseUrl: http://localhost:7007
4 |
5 | backend:
6 | # Note that the baseUrl should be the URL that the browser and other clients
7 | # should use when communicating with the backend, i.e. it needs to be
8 | # reachable not just from within the backend host, but from all of your
9 | # callers. When its value is "http://localhost:7007", it's strictly private
10 | # and can't be reached by others.
11 | baseUrl: http://localhost:7007
12 | # The listener can also be expressed as a single : string. In this case we bind to
13 | # all interfaces, the most permissive setting. The right value depends on your specific deployment.
14 | listen: ':7007'
15 |
16 | # config options: https://node-postgres.com/api/client
17 | database:
18 | client: pg
19 | connection:
20 | host: ${POSTGRES_HOST}
21 | port: ${POSTGRES_PORT}
22 | user: ${POSTGRES_USER}
23 | password: ${POSTGRES_PASSWORD}
24 | # https://node-postgres.com/features/ssl
25 | # you can set the sslmode configuration option via the `PGSSLMODE` environment variable
26 | # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require)
27 | # ssl:
28 | # ca: # if you have a CA file and want to verify it you can uncomment this section
29 | # $file: /ca/server.crt
30 |
31 | catalog:
32 | # Overrides the default list locations from app-config.yaml as these contain example data.
33 | # See https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog for more details
34 | # on how to get entities into the catalog.
35 | locations: []
36 |
--------------------------------------------------------------------------------
/src/backstage/app-config.yaml:
--------------------------------------------------------------------------------
1 | app:
2 | title: Scaffolded Backstage App
3 | baseUrl: http://localhost:3000
4 |
5 | organization:
6 | name: My Company
7 |
8 | backend:
9 | # Used for enabling authentication, secret is shared by all backend plugins
10 | # See https://backstage.io/docs/tutorials/backend-to-backend-auth for
11 | # information on the format
12 | # auth:
13 | # keys:
14 | # - secret: ${BACKEND_SECRET}
15 | baseUrl: http://localhost:7007
16 | listen:
17 | port: 7007
18 | # Uncomment the following host directive to bind to specific interfaces
19 | # host: 127.0.0.1
20 | csp:
21 | connect-src: ["'self'", 'http:', 'https:']
22 | # Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
23 | # Default Helmet Content-Security-Policy values can be removed by setting the key to false
24 | cors:
25 | origin: http://localhost:3000
26 | methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
27 | credentials: true
28 | # This is for local development only, it is not recommended to use this in production
29 | # The production database configuration is stored in app-config.production.yaml
30 | database:
31 | client: better-sqlite3
32 | connection: ':memory:'
33 | cache:
34 | store: memory
35 | # workingDirectory: /tmp # Use this to configure a working directory for the scaffolder, defaults to the OS temp-dir
36 |
37 | integrations:
38 | github:
39 | - host: github.com
40 | # This is a Personal Access Token or PAT from GitHub. You can find out how to generate this token, and more information
41 | # about setting up the GitHub integration here: https://backstage.io/docs/getting-started/configuration#setting-up-a-github-integration
42 | token: ${GITHUB_TOKEN}
43 | ### Example for how to add your GitHub Enterprise instance using the API:
44 | # - host: ghe.example.net
45 | # apiBaseUrl: https://ghe.example.net/api/v3
46 | # token: ${GHE_TOKEN}
47 |
48 | proxy:
49 | '/test':
50 | target: 'https://example.com'
51 | changeOrigin: true
52 |
53 | # Reference documentation http://backstage.io/docs/features/techdocs/configuration
54 | # Note: After experimenting with basic setup, use CI/CD to generate docs
55 | # and an external cloud storage when deploying TechDocs for production use-case.
56 | # https://backstage.io/docs/features/techdocs/how-to-guides#how-to-migrate-from-techdocs-basic-to-recommended-deployment-approach
57 | techdocs:
58 | builder: 'local' # Alternatives - 'external'
59 | generator:
60 | runIn: 'docker' # Alternatives - 'local'
61 | publisher:
62 | type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.
63 |
64 | auth:
65 | # see https://backstage.io/docs/auth/ to learn about auth providers
66 | providers: {}
67 |
68 | scaffolder:
69 | # see https://backstage.io/docs/features/software-templates/configuration for software template options
70 |
71 | catalog:
72 | import:
73 | entityFilename: catalog-info.yaml
74 | pullRequestBranchName: backstage-integration
75 | rules:
76 | - allow: [Component, System, API, Resource, Location]
77 | locations:
78 | # Local example data, file locations are relative to the backend process, typically `packages/backend`
79 | - type: file
80 | target: ../../examples/entities.yaml
81 |
82 | # Local example template
83 | - type: file
84 | target: ../../examples/template/template.yaml
85 | rules:
86 | - allow: [Template]
87 |
88 | # Local example organizational data
89 | - type: file
90 | target: ../../examples/org.yaml
91 | rules:
92 | - allow: [User, Group]
93 |
94 | ## Uncomment these lines to add more example data
95 | # - type: url
96 | # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/all.yaml
97 |
98 | ## Uncomment these lines to add an example org
99 | # - type: url
100 | # target: https://github.com/backstage/backstage/blob/master/packages/catalog-model/examples/acme-corp.yaml
101 | # rules:
102 | # - allow: [User, Group]
103 |
--------------------------------------------------------------------------------
/src/backstage/backstage.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.8.0"
3 | }
4 |
--------------------------------------------------------------------------------
/src/backstage/catalog-info.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: backstage.io/v1alpha1
2 | kind: Component
3 | metadata:
4 | name: backstage
5 | description: An example of a Backstage application.
6 | # Example for optional annotations
7 | # annotations:
8 | # github.com/project-slug: backstage/backstage
9 | # backstage.io/techdocs-ref: dir:.
10 | spec:
11 | type: website
12 | owner: john@example.com
13 | lifecycle: experimental
14 |
--------------------------------------------------------------------------------
/src/backstage/examples/entities.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-system
3 | apiVersion: backstage.io/v1alpha1
4 | kind: System
5 | metadata:
6 | name: examples
7 | spec:
8 | owner: guests
9 | ---
10 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-component
11 | apiVersion: backstage.io/v1alpha1
12 | kind: Component
13 | metadata:
14 | name: example-website
15 | spec:
16 | type: website
17 | lifecycle: experimental
18 | owner: guests
19 | system: examples
20 | providesApis: [example-grpc-api]
21 | ---
22 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-api
23 | apiVersion: backstage.io/v1alpha1
24 | kind: API
25 | metadata:
26 | name: example-grpc-api
27 | spec:
28 | type: grpc
29 | lifecycle: experimental
30 | owner: guests
31 | system: examples
32 | definition: |
33 | syntax = "proto3";
34 |
35 | service Exampler {
36 | rpc Example (ExampleMessage) returns (ExampleMessage) {};
37 | }
38 |
39 | message ExampleMessage {
40 | string example = 1;
41 | };
42 |
--------------------------------------------------------------------------------
/src/backstage/examples/org.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-user
3 | apiVersion: backstage.io/v1alpha1
4 | kind: User
5 | metadata:
6 | name: guest
7 | spec:
8 | memberOf: [guests]
9 | ---
10 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-group
11 | apiVersion: backstage.io/v1alpha1
12 | kind: Group
13 | metadata:
14 | name: guests
15 | spec:
16 | type: team
17 | children: []
18 |
--------------------------------------------------------------------------------
/src/backstage/examples/template/content/catalog-info.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: backstage.io/v1alpha1
2 | kind: Component
3 | metadata:
4 | name: ${{ values.name | dump }}
5 | spec:
6 | type: service
7 | owner: user:guest
8 | lifecycle: experimental
9 |
--------------------------------------------------------------------------------
/src/backstage/examples/template/content/index.js:
--------------------------------------------------------------------------------
1 | console.log('Hello from ${{ values.name }}!');
2 |
--------------------------------------------------------------------------------
/src/backstage/examples/template/content/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "${{ values.name }}",
3 | "private": true,
4 | "dependencies": {}
5 | }
6 |
--------------------------------------------------------------------------------
/src/backstage/examples/template/template.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: scaffolder.backstage.io/v1beta3
2 | # https://backstage.io/docs/features/software-catalog/descriptor-format#kind-template
3 | kind: Template
4 | metadata:
5 | name: example-nodejs-template
6 | title: Example Node.js Template
7 | description: An example template for the scaffolder that creates a simple Node.js service
8 | spec:
9 | owner: user:guest
10 | type: service
11 |
12 | # These parameters are used to generate the input form in the frontend, and are
13 | # used to gather input data for the execution of the template.
14 | parameters:
15 | - title: Fill in some steps
16 | required:
17 | - name
18 | properties:
19 | name:
20 | title: Name
21 | type: string
22 | description: Unique name of the component
23 | ui:autofocus: true
24 | ui:options:
25 | rows: 5
26 | - title: Choose a location
27 | required:
28 | - repoUrl
29 | properties:
30 | repoUrl:
31 | title: Repository Location
32 | type: string
33 | ui:field: RepoUrlPicker
34 | ui:options:
35 | allowedHosts:
36 | - github.com
37 |
38 | # These steps are executed in the scaffolder backend, using data that we gathered
39 | # via the parameters above.
40 | steps:
41 | # Each step executes an action, in this case one templates files into the working directory.
42 | - id: fetch-base
43 | name: Fetch Base
44 | action: fetch:template
45 | input:
46 | url: ./content
47 | values:
48 | name: ${{ parameters.name }}
49 |
50 | # This step publishes the contents of the working directory to GitHub.
51 | - id: publish
52 | name: Publish
53 | action: publish:github
54 | input:
55 | allowedHosts: ['github.com']
56 | description: This is ${{ parameters.name }}
57 | repoUrl: ${{ parameters.repoUrl }}
58 |
59 | # The final step is to register our new component in the catalog.
60 | - id: register
61 | name: Register
62 | action: catalog:register
63 | input:
64 | repoContentsUrl: ${{ steps.publish.output.repoContentsUrl }}
65 | catalogInfoPath: '/catalog-info.yaml'
66 |
67 | # Outputs are displayed to the user after a successful execution of the template.
68 | output:
69 | links:
70 | - title: Repository
71 | url: ${{ steps.publish.output.remoteUrl }}
72 | - title: Open in catalog
73 | icon: catalog
74 | entityRef: ${{ steps.register.output.entityRef }}
75 |
--------------------------------------------------------------------------------
/src/backstage/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": ["packages/*", "plugins/*"],
3 | "npmClient": "yarn",
4 | "useWorkspaces": true,
5 | "version": "0.1.0"
6 | }
7 |
--------------------------------------------------------------------------------
/src/backstage/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "version": "1.0.0",
4 | "private": true,
5 | "engines": {
6 | "node": "16 || 18"
7 | },
8 | "scripts": {
9 | "dev": "concurrently \"yarn start\" \"yarn start-backend\"",
10 | "start": "yarn workspace app start",
11 | "start-backend": "yarn workspace backend start",
12 | "build:backend": "yarn workspace backend build",
13 | "build:all": "backstage-cli repo build --all",
14 | "build-image": "yarn workspace backend build-image",
15 | "tsc": "tsc",
16 | "tsc:full": "tsc --skipLibCheck false --incremental false",
17 | "clean": "backstage-cli repo clean",
18 | "test": "backstage-cli repo test",
19 | "test:all": "backstage-cli repo test --coverage",
20 | "lint": "backstage-cli repo lint --since origin/master",
21 | "lint:all": "backstage-cli repo lint",
22 | "prettier:check": "prettier --check .",
23 | "create-plugin": "backstage-cli create-plugin --scope internal",
24 | "new": "backstage-cli new --scope internal"
25 | },
26 | "workspaces": {
27 | "packages": [
28 | "packages/*",
29 | "plugins/*"
30 | ]
31 | },
32 | "devDependencies": {
33 | "@backstage/cli": "^0.21.0",
34 | "@spotify/prettier-config": "^12.0.0",
35 | "concurrently": "^6.0.0",
36 | "lerna": "^4.0.0",
37 | "prettier": "^2.3.2",
38 | "typescript": "~4.6.4",
39 | "node-gyp": "^9.0.0"
40 | },
41 | "resolutions": {
42 | "@types/react": "^17",
43 | "@types/react-dom": "^17"
44 | },
45 | "prettier": "@spotify/prettier-config",
46 | "lint-staged": {
47 | "*.{js,jsx,ts,tsx,mjs,cjs}": [
48 | "eslint --fix",
49 | "prettier --write"
50 | ],
51 | "*.{json,md}": [
52 | "prettier --write"
53 | ]
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/backstage/packages/README.md:
--------------------------------------------------------------------------------
1 | # The Packages Folder
2 |
3 | This is where your own applications and centrally managed libraries live, each
4 | in a separate folder of its own.
5 |
6 | From the start there's an `app` folder (for the frontend) and a `backend` folder
7 | (for the Node backend), but you can also add more modules in here that house
8 | your core additions and adaptations, such as themes, common React component
9 | libraries, utilities, and similar.
10 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
2 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost:3001",
3 | "fixturesFolder": false,
4 | "pluginsFile": false,
5 | "retries": 3
6 | }
7 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/cypress/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["cypress"],
3 | "extends": ["plugin:cypress/recommended"],
4 | "rules": {
5 | "jest/expect-expect": [
6 | "error",
7 | {
8 | "assertFunctionNames": ["expect", "cy.contains"]
9 | }
10 | ],
11 | "import/no-extraneous-dependencies": [
12 | "error",
13 | {
14 | "devDependencies": true,
15 | "optionalDependencies": true,
16 | "peerDependencies": true,
17 | "bundledDependencies": true
18 | }
19 | ]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/cypress/integration/app.js:
--------------------------------------------------------------------------------
1 | describe('App', () => {
2 | it('should render the catalog', () => {
3 | cy.visit('/');
4 | cy.contains('My Company Catalog');
5 | });
6 | });
7 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "0.0.0",
4 | "private": true,
5 | "bundled": true,
6 | "backstage": {
7 | "role": "frontend"
8 | },
9 | "scripts": {
10 | "start": "backstage-cli package start",
11 | "build": "backstage-cli package build",
12 | "clean": "backstage-cli package clean",
13 | "test": "backstage-cli package test",
14 | "lint": "backstage-cli package lint",
15 | "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev",
16 | "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run",
17 | "cy:dev": "cypress open",
18 | "cy:run": "cypress run --browser chrome"
19 | },
20 | "dependencies": {
21 | "@backstage/app-defaults": "^1.0.8",
22 | "@backstage/catalog-model": "^1.1.3",
23 | "@backstage/cli": "^0.21.0",
24 | "@backstage/core-app-api": "^1.2.0",
25 | "@backstage/core-components": "^0.12.0",
26 | "@backstage/core-plugin-api": "^1.1.0",
27 | "@backstage/integration-react": "^1.1.6",
28 | "@backstage/plugin-api-docs": "^0.8.11",
29 | "@backstage/plugin-catalog": "^1.6.1",
30 | "@backstage/plugin-catalog-common": "^1.0.8",
31 | "@backstage/plugin-catalog-graph": "^0.2.23",
32 | "@backstage/plugin-catalog-import": "^0.9.1",
33 | "@backstage/plugin-catalog-react": "^1.2.1",
34 | "@backstage/plugin-github-actions": "^0.5.11",
35 | "@backstage/plugin-org": "^0.6.0",
36 | "@backstage/plugin-permission-react": "^0.4.7",
37 | "@backstage/plugin-scaffolder": "^1.8.0",
38 | "@backstage/plugin-search": "^1.0.4",
39 | "@backstage/plugin-search-react": "^1.2.1",
40 | "@backstage/plugin-tech-radar": "^0.5.18",
41 | "@backstage/plugin-techdocs": "^1.4.0",
42 | "@backstage/plugin-techdocs-react": "^1.0.6",
43 | "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.6",
44 | "@backstage/plugin-user-settings": "^0.5.1",
45 | "@backstage/theme": "^0.2.16",
46 | "@material-ui/core": "^4.12.2",
47 | "@material-ui/icons": "^4.9.1",
48 | "history": "^5.0.0",
49 | "react": "^17.0.2",
50 | "react-dom": "^17.0.2",
51 | "react-router": "^6.3.0",
52 | "react-router-dom": "^6.3.0",
53 | "react-use": "^17.2.4"
54 | },
55 | "devDependencies": {
56 | "@backstage/test-utils": "^1.2.2",
57 | "@testing-library/jest-dom": "^5.10.1",
58 | "@testing-library/react": "^12.1.3",
59 | "@testing-library/user-event": "^14.0.0",
60 | "@types/node": "^16.11.26",
61 | "@types/react-dom": "*",
62 | "cross-env": "^7.0.0",
63 | "cypress": "^9.7.0",
64 | "eslint-plugin-cypress": "^2.10.3",
65 | "start-server-and-test": "^1.10.11"
66 | },
67 | "browserslist": {
68 | "production": [
69 | ">0.2%",
70 | "not dead",
71 | "not op_mini all"
72 | ],
73 | "development": [
74 | "last 1 chrome version",
75 | "last 1 firefox version",
76 | "last 1 safari version"
77 | ]
78 | },
79 | "files": [
80 | "dist"
81 | ]
82 | }
83 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolomainardi/docker-backstage-devcontainers/c0b1356c1e86b8c41c7db86bdf6287e990d50eec/src/backstage/packages/app/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/src/backstage/packages/app/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolomainardi/docker-backstage-devcontainers/c0b1356c1e86b8c41c7db86bdf6287e990d50eec/src/backstage/packages/app/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/backstage/packages/app/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolomainardi/docker-backstage-devcontainers/c0b1356c1e86b8c41c7db86bdf6287e990d50eec/src/backstage/packages/app/public/favicon-16x16.png
--------------------------------------------------------------------------------
/src/backstage/packages/app/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolomainardi/docker-backstage-devcontainers/c0b1356c1e86b8c41c7db86bdf6287e990d50eec/src/backstage/packages/app/public/favicon-32x32.png
--------------------------------------------------------------------------------
/src/backstage/packages/app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/paolomainardi/docker-backstage-devcontainers/c0b1356c1e86b8c41c7db86bdf6287e990d50eec/src/backstage/packages/app/public/favicon.ico
--------------------------------------------------------------------------------
/src/backstage/packages/app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
16 |
21 |
22 |
23 |
28 |
34 |
40 |
45 | <%= config.getString('app.title') %>
46 |
47 |
48 |
49 |
50 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Backstage",
3 | "name": "Backstage",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "48x48",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/public/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { renderWithEffects } from '@backstage/test-utils';
3 | import App from './App';
4 |
5 | describe('App', () => {
6 | it('should render', async () => {
7 | process.env = {
8 | NODE_ENV: 'test',
9 | APP_CONFIG: [
10 | {
11 | data: {
12 | app: { title: 'Test' },
13 | backend: { baseUrl: 'http://localhost:7007' },
14 | techdocs: {
15 | storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
16 | },
17 | },
18 | context: 'test',
19 | },
20 | ] as any,
21 | };
22 |
23 | const rendered = await renderWithEffects();
24 | expect(rendered.baseElement).toBeInTheDocument();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Navigate, Route } from 'react-router';
3 | import { apiDocsPlugin, ApiExplorerPage } from '@backstage/plugin-api-docs';
4 | import {
5 | CatalogEntityPage,
6 | CatalogIndexPage,
7 | catalogPlugin,
8 | } from '@backstage/plugin-catalog';
9 | import {
10 | CatalogImportPage,
11 | catalogImportPlugin,
12 | } from '@backstage/plugin-catalog-import';
13 | import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder';
14 | import { orgPlugin } from '@backstage/plugin-org';
15 | import { SearchPage } from '@backstage/plugin-search';
16 | import { TechRadarPage } from '@backstage/plugin-tech-radar';
17 | import {
18 | TechDocsIndexPage,
19 | techdocsPlugin,
20 | TechDocsReaderPage,
21 | } from '@backstage/plugin-techdocs';
22 | import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
23 | import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
24 | import { UserSettingsPage } from '@backstage/plugin-user-settings';
25 | import { apis } from './apis';
26 | import { entityPage } from './components/catalog/EntityPage';
27 | import { searchPage } from './components/search/SearchPage';
28 | import { Root } from './components/Root';
29 |
30 | import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components';
31 | import { createApp } from '@backstage/app-defaults';
32 | import { FlatRoutes } from '@backstage/core-app-api';
33 | import { CatalogGraphPage } from '@backstage/plugin-catalog-graph';
34 | import { RequirePermission } from '@backstage/plugin-permission-react';
35 | import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common/alpha';
36 |
37 | const app = createApp({
38 | apis,
39 | bindRoutes({ bind }) {
40 | bind(catalogPlugin.externalRoutes, {
41 | createComponent: scaffolderPlugin.routes.root,
42 | viewTechDoc: techdocsPlugin.routes.docRoot,
43 | });
44 | bind(apiDocsPlugin.externalRoutes, {
45 | registerApi: catalogImportPlugin.routes.importPage,
46 | });
47 | bind(scaffolderPlugin.externalRoutes, {
48 | registerComponent: catalogImportPlugin.routes.importPage,
49 | });
50 | bind(orgPlugin.externalRoutes, {
51 | catalogIndex: catalogPlugin.routes.catalogIndex,
52 | });
53 | },
54 | });
55 |
56 | const AppProvider = app.getProvider();
57 | const AppRouter = app.getRouter();
58 |
59 | const routes = (
60 |
61 | } />
62 | } />
63 | }
66 | >
67 | {entityPage}
68 |
69 | } />
70 | }
73 | >
74 |
75 |
76 |
77 |
78 | } />
79 | } />
80 | }
83 | />
84 |
88 |
89 |
90 | }
91 | />
92 | }>
93 | {searchPage}
94 |
95 | } />
96 | } />
97 |
98 | );
99 |
100 | const App = () => (
101 |
102 |
103 |
104 |
105 | {routes}
106 |
107 |
108 | );
109 |
110 | export default App;
111 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/apis.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ScmIntegrationsApi,
3 | scmIntegrationsApiRef,
4 | ScmAuth,
5 | } from '@backstage/integration-react';
6 | import {
7 | AnyApiFactory,
8 | configApiRef,
9 | createApiFactory,
10 | } from '@backstage/core-plugin-api';
11 |
12 | export const apis: AnyApiFactory[] = [
13 | createApiFactory({
14 | api: scmIntegrationsApiRef,
15 | deps: { configApi: configApiRef },
16 | factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
17 | }),
18 | ScmAuth.createDefaultApiFactory(),
19 | ];
20 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/components/Root/LogoFull.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core';
3 |
4 | const useStyles = makeStyles({
5 | svg: {
6 | width: 'auto',
7 | height: 30,
8 | },
9 | path: {
10 | fill: '#7df3e1',
11 | },
12 | });
13 | const LogoFull = () => {
14 | const classes = useStyles();
15 |
16 | return (
17 |
27 | );
28 | };
29 |
30 | export default LogoFull;
31 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/components/Root/LogoIcon.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core';
3 |
4 | const useStyles = makeStyles({
5 | svg: {
6 | width: 'auto',
7 | height: 28,
8 | },
9 | path: {
10 | fill: '#7df3e1',
11 | },
12 | });
13 |
14 | const LogoIcon = () => {
15 | const classes = useStyles();
16 |
17 | return (
18 |
28 | );
29 | };
30 |
31 | export default LogoIcon;
32 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/components/Root/Root.tsx:
--------------------------------------------------------------------------------
1 | import React, { PropsWithChildren } from 'react';
2 | import { makeStyles } from '@material-ui/core';
3 | import HomeIcon from '@material-ui/icons/Home';
4 | import ExtensionIcon from '@material-ui/icons/Extension';
5 | import MapIcon from '@material-ui/icons/MyLocation';
6 | import LibraryBooks from '@material-ui/icons/LibraryBooks';
7 | import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
8 | import LogoFull from './LogoFull';
9 | import LogoIcon from './LogoIcon';
10 | import {
11 | Settings as SidebarSettings,
12 | UserSettingsSignInAvatar,
13 | } from '@backstage/plugin-user-settings';
14 | import { SidebarSearchModal } from '@backstage/plugin-search';
15 | import {
16 | Sidebar,
17 | sidebarConfig,
18 | SidebarDivider,
19 | SidebarGroup,
20 | SidebarItem,
21 | SidebarPage,
22 | SidebarScrollWrapper,
23 | SidebarSpace,
24 | useSidebarOpenState,
25 | Link,
26 | } from '@backstage/core-components';
27 | import MenuIcon from '@material-ui/icons/Menu';
28 | import SearchIcon from '@material-ui/icons/Search';
29 |
30 | const useSidebarLogoStyles = makeStyles({
31 | root: {
32 | width: sidebarConfig.drawerWidthClosed,
33 | height: 3 * sidebarConfig.logoHeight,
34 | display: 'flex',
35 | flexFlow: 'row nowrap',
36 | alignItems: 'center',
37 | marginBottom: -14,
38 | },
39 | link: {
40 | width: sidebarConfig.drawerWidthClosed,
41 | marginLeft: 24,
42 | },
43 | });
44 |
45 | const SidebarLogo = () => {
46 | const classes = useSidebarLogoStyles();
47 | const { isOpen } = useSidebarOpenState();
48 |
49 | return (
50 |
51 |
52 | {isOpen ? : }
53 |
54 |
55 | );
56 | };
57 |
58 | export const Root = ({ children }: PropsWithChildren<{}>) => (
59 |
60 |
61 |
62 | } to="/search">
63 |
64 |
65 |
66 | }>
67 | {/* Global nav, not org-specific */}
68 |
69 |
70 |
71 |
72 | {/* End global nav */}
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | }
83 | to="/settings"
84 | >
85 |
86 |
87 |
88 | {children}
89 |
90 | );
91 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/components/Root/index.ts:
--------------------------------------------------------------------------------
1 | export { Root } from './Root';
2 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/components/catalog/EntityPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Button, Grid } from '@material-ui/core';
3 | import {
4 | EntityApiDefinitionCard,
5 | EntityConsumedApisCard,
6 | EntityConsumingComponentsCard,
7 | EntityHasApisCard,
8 | EntityProvidedApisCard,
9 | EntityProvidingComponentsCard,
10 | } from '@backstage/plugin-api-docs';
11 | import {
12 | EntityAboutCard,
13 | EntityDependsOnComponentsCard,
14 | EntityDependsOnResourcesCard,
15 | EntityHasComponentsCard,
16 | EntityHasResourcesCard,
17 | EntityHasSubcomponentsCard,
18 | EntityHasSystemsCard,
19 | EntityLayout,
20 | EntityLinksCard,
21 | EntitySwitch,
22 | EntityOrphanWarning,
23 | EntityProcessingErrorsPanel,
24 | isComponentType,
25 | isKind,
26 | hasCatalogProcessingErrors,
27 | isOrphan,
28 | } from '@backstage/plugin-catalog';
29 | import {
30 | isGithubActionsAvailable,
31 | EntityGithubActionsContent,
32 | } from '@backstage/plugin-github-actions';
33 | import {
34 | EntityUserProfileCard,
35 | EntityGroupProfileCard,
36 | EntityMembersListCard,
37 | EntityOwnershipCard,
38 | } from '@backstage/plugin-org';
39 | import { EntityTechdocsContent } from '@backstage/plugin-techdocs';
40 | import { EmptyState } from '@backstage/core-components';
41 | import {
42 | Direction,
43 | EntityCatalogGraphCard,
44 | } from '@backstage/plugin-catalog-graph';
45 | import {
46 | RELATION_API_CONSUMED_BY,
47 | RELATION_API_PROVIDED_BY,
48 | RELATION_CONSUMES_API,
49 | RELATION_DEPENDENCY_OF,
50 | RELATION_DEPENDS_ON,
51 | RELATION_HAS_PART,
52 | RELATION_PART_OF,
53 | RELATION_PROVIDES_API,
54 | } from '@backstage/catalog-model';
55 |
56 | import { TechDocsAddons } from '@backstage/plugin-techdocs-react';
57 | import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib';
58 |
59 | const techdocsContent = (
60 |
61 |
62 |
63 |
64 |
65 | );
66 |
67 | const cicdContent = (
68 | // This is an example of how you can implement your company's logic in entity page.
69 | // You can for example enforce that all components of type 'service' should use GitHubActions
70 |
71 |
72 |
73 |
74 |
75 |
76 |
86 | Read more
87 |
88 | }
89 | />
90 |
91 |
92 | );
93 |
94 | const entityWarningContent = (
95 | <>
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 | >
112 | );
113 |
114 | const overviewContent = (
115 |
116 | {entityWarningContent}
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 | );
132 |
133 | const serviceEntityPage = (
134 |
135 |
136 | {overviewContent}
137 |
138 |
139 |
140 | {cicdContent}
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 | {techdocsContent}
167 |
168 |
169 | );
170 |
171 | const websiteEntityPage = (
172 |
173 |
174 | {overviewContent}
175 |
176 |
177 |
178 | {cicdContent}
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 | {techdocsContent}
194 |
195 |
196 | );
197 |
198 | /**
199 | * NOTE: This page is designed to work on small screens such as mobile devices.
200 | * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`,
201 | * since this does not default. If no breakpoints are used, the items will equitably share the available space.
202 | * https://material-ui.com/components/grid/#basic-grid.
203 | */
204 |
205 | const defaultEntityPage = (
206 |
207 |
208 | {overviewContent}
209 |
210 |
211 |
212 | {techdocsContent}
213 |
214 |
215 | );
216 |
217 | const componentPage = (
218 |
219 |
220 | {serviceEntityPage}
221 |
222 |
223 |
224 | {websiteEntityPage}
225 |
226 |
227 | {defaultEntityPage}
228 |
229 | );
230 |
231 | const apiPage = (
232 |
233 |
234 |
235 | {entityWarningContent}
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 | );
265 |
266 | const userPage = (
267 |
268 |
269 |
270 | {entityWarningContent}
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 | );
281 |
282 | const groupPage = (
283 |
284 |
285 |
286 | {entityWarningContent}
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 | );
300 |
301 | const systemPage = (
302 |
303 |
304 |
305 | {entityWarningContent}
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
344 |
345 |
346 | );
347 |
348 | const domainPage = (
349 |
350 |
351 |
352 | {entityWarningContent}
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 | );
366 |
367 | export const entityPage = (
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 | {defaultEntityPage}
377 |
378 | );
379 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/components/search/SearchPage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core';
3 |
4 | import { CatalogSearchResultListItem } from '@backstage/plugin-catalog';
5 | import {
6 | catalogApiRef,
7 | CATALOG_FILTER_EXISTS,
8 | } from '@backstage/plugin-catalog-react';
9 | import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs';
10 |
11 | import { SearchType } from '@backstage/plugin-search';
12 | import {
13 | DefaultResultListItem,
14 | SearchBar,
15 | SearchFilter,
16 | SearchResult,
17 | SearchPagination,
18 | useSearch,
19 | } from '@backstage/plugin-search-react';
20 | import {
21 | CatalogIcon,
22 | Content,
23 | DocsIcon,
24 | Header,
25 | Page,
26 | } from '@backstage/core-components';
27 | import { useApi } from '@backstage/core-plugin-api';
28 |
29 | const useStyles = makeStyles((theme: Theme) => ({
30 | bar: {
31 | padding: theme.spacing(1, 0),
32 | },
33 | filters: {
34 | padding: theme.spacing(2),
35 | marginTop: theme.spacing(2),
36 | },
37 | filter: {
38 | '& + &': {
39 | marginTop: theme.spacing(2.5),
40 | },
41 | },
42 | }));
43 |
44 | const SearchPage = () => {
45 | const classes = useStyles();
46 | const { types } = useSearch();
47 | const catalogApi = useApi(catalogApiRef);
48 |
49 | return (
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | ,
68 | },
69 | {
70 | value: 'techdocs',
71 | name: 'Documentation',
72 | icon: ,
73 | },
74 | ]}
75 | />
76 |
77 | {types.includes('techdocs') && (
78 | {
83 | // Return a list of entities which are documented.
84 | const { items } = await catalogApi.getEntities({
85 | fields: ['metadata.name'],
86 | filter: {
87 | 'metadata.annotations.backstage.io/techdocs-ref':
88 | CATALOG_FILTER_EXISTS,
89 | },
90 | });
91 |
92 | const names = items.map(entity => entity.metadata.name);
93 | names.sort();
94 | return names;
95 | }}
96 | />
97 | )}
98 |
104 |
110 |
111 |
112 |
113 |
114 |
115 | {({ results }) => (
116 |
117 | {results.map(({ type, document, highlight, rank }) => {
118 | switch (type) {
119 | case 'software-catalog':
120 | return (
121 |
127 | );
128 | case 'techdocs':
129 | return (
130 |
136 | );
137 | default:
138 | return (
139 |
145 | );
146 | }
147 | })}
148 |
149 | )}
150 |
151 |
152 |
153 |
154 |
155 | );
156 | };
157 |
158 | export const searchPage = ;
159 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/index.tsx:
--------------------------------------------------------------------------------
1 | import '@backstage/cli/asset-types';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom';
4 | import App from './App';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/src/backstage/packages/app/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom';
2 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
2 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/Dockerfile:
--------------------------------------------------------------------------------
1 | # This dockerfile builds an image for the backend package.
2 | # It should be executed with the root of the repo as docker context.
3 | #
4 | # Before building this image, be sure to have run the following commands in the repo root:
5 | #
6 | # yarn install
7 | # yarn tsc
8 | # yarn build:backend
9 | #
10 | # Once the commands have been run, you can build the image using `yarn build-image`
11 |
12 | FROM node:16-bullseye-slim
13 |
14 | # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
15 | # in which case you should also move better-sqlite3 to "devDependencies" in package.json.
16 | RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
17 | --mount=type=cache,target=/var/lib/apt,sharing=locked \
18 | apt-get update && \
19 | apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \
20 | yarn config set python /usr/bin/python3
21 |
22 | # From here on we use the least-privileged `node` user to run the backend.
23 | USER node
24 |
25 | # This should create the app dir as `node`.
26 | # If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`.
27 | # If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`.
28 | WORKDIR /app
29 |
30 | # This switches many Node.js dependencies to production mode.
31 | ENV NODE_ENV production
32 |
33 | # Copy repo skeleton first, to avoid unnecessary docker cache invalidation.
34 | # The skeleton contains the package.json of each package in the monorepo,
35 | # and along with yarn.lock and the root package.json, that's enough to run yarn install.
36 | COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
37 | RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
38 |
39 | RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
40 | yarn install --frozen-lockfile --production --network-timeout 300000
41 |
42 | # Then copy the rest of the backend bundle, along with any other files we might want.
43 | COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./
44 | RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
45 |
46 | CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]
47 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/README.md:
--------------------------------------------------------------------------------
1 | # example-backend
2 |
3 | This package is an EXAMPLE of a Backstage backend.
4 |
5 | The main purpose of this package is to provide a test bed for Backstage plugins
6 | that have a backend part. Feel free to experiment locally or within your fork by
7 | adding dependencies and routes to this backend, to try things out.
8 |
9 | Our goal is to eventually amend the create-app flow of the CLI, such that a
10 | production ready version of a backend skeleton is made alongside the frontend
11 | app. Until then, feel free to experiment here!
12 |
13 | ## Development
14 |
15 | To run the example backend, first go to the project root and run
16 |
17 | ```bash
18 | yarn install
19 | ```
20 |
21 | You should only need to do this once.
22 |
23 | After that, go to the `packages/backend` directory and run
24 |
25 | ```bash
26 | yarn start
27 | ```
28 |
29 | If you want to override any configuration locally, for example adding any secrets,
30 | you can do so in `app-config.local.yaml`.
31 |
32 | The backend starts up on port 7007 per default.
33 |
34 | ## Populating The Catalog
35 |
36 | If you want to use the catalog functionality, you need to add so called
37 | locations to the backend. These are places where the backend can find some
38 | entity descriptor data to consume and serve. For more information, see
39 | [Software Catalog Overview - Adding Components to the Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview#adding-components-to-the-catalog).
40 |
41 | To get started quickly, this template already includes some statically configured example locations
42 | in `app-config.yaml` under `catalog.locations`. You can remove and replace these locations as you
43 | like, and also override them for local development in `app-config.local.yaml`.
44 |
45 | ## Authentication
46 |
47 | We chose [Passport](http://www.passportjs.org/) as authentication platform due
48 | to its comprehensive set of supported authentication
49 | [strategies](http://www.passportjs.org/packages/).
50 |
51 | Read more about the
52 | [auth-backend](https://github.com/backstage/backstage/blob/master/plugins/auth-backend/README.md)
53 | and
54 | [how to add a new provider](https://github.com/backstage/backstage/blob/master/docs/auth/add-auth-provider.md)
55 |
56 | ## Documentation
57 |
58 | - [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
59 | - [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md)
60 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "0.0.0",
4 | "main": "dist/index.cjs.js",
5 | "types": "src/index.ts",
6 | "private": true,
7 | "backstage": {
8 | "role": "backend"
9 | },
10 | "scripts": {
11 | "start": "backstage-cli package start",
12 | "build": "backstage-cli package build",
13 | "lint": "backstage-cli package lint",
14 | "test": "backstage-cli package test",
15 | "clean": "backstage-cli package clean",
16 | "build-image": "docker build ../.. -f Dockerfile --tag backstage"
17 | },
18 | "dependencies": {
19 | "@backstage/backend-common": "^0.16.0",
20 | "@backstage/backend-tasks": "^0.3.7",
21 | "@backstage/catalog-client": "^1.1.2",
22 | "@backstage/catalog-model": "^1.1.3",
23 | "@backstage/config": "^1.0.4",
24 | "@backstage/plugin-app-backend": "^0.3.38",
25 | "@backstage/plugin-auth-backend": "^0.17.1",
26 | "@backstage/plugin-auth-node": "^0.2.7",
27 | "@backstage/plugin-catalog-backend": "^1.5.1",
28 | "@backstage/plugin-permission-common": "^0.7.1",
29 | "@backstage/plugin-permission-node": "^0.7.1",
30 | "@backstage/plugin-proxy-backend": "^0.2.32",
31 | "@backstage/plugin-scaffolder-backend": "^1.8.0",
32 | "@backstage/plugin-search-backend": "^1.1.1",
33 | "@backstage/plugin-search-backend-module-pg": "^0.4.2",
34 | "@backstage/plugin-search-backend-node": "^1.0.4",
35 | "@backstage/plugin-techdocs-backend": "^1.4.1",
36 | "app": "link:../app",
37 | "better-sqlite3": "^7.5.0",
38 | "dockerode": "^3.3.1",
39 | "express": "^4.17.1",
40 | "express-promise-router": "^4.1.0",
41 | "pg": "^8.8.0",
42 | "winston": "^3.2.1"
43 | },
44 | "devDependencies": {
45 | "@backstage/cli": "^0.21.0",
46 | "@types/dockerode": "^3.3.0",
47 | "@types/express": "^4.17.6",
48 | "@types/express-serve-static-core": "^4.17.5",
49 | "@types/luxon": "^2.0.4"
50 | },
51 | "files": [
52 | "dist"
53 | ]
54 | }
55 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/index.test.ts:
--------------------------------------------------------------------------------
1 | import { PluginEnvironment } from './types';
2 |
3 | describe('test', () => {
4 | it('unbreaks the test runner', () => {
5 | const unbreaker = {} as PluginEnvironment;
6 | expect(unbreaker).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Hi!
3 | *
4 | * Note that this is an EXAMPLE Backstage backend. Please check the README.
5 | *
6 | * Happy hacking!
7 | */
8 |
9 | import Router from 'express-promise-router';
10 | import {
11 | createServiceBuilder,
12 | loadBackendConfig,
13 | getRootLogger,
14 | useHotMemoize,
15 | notFoundHandler,
16 | CacheManager,
17 | DatabaseManager,
18 | SingleHostDiscovery,
19 | UrlReaders,
20 | ServerTokenManager,
21 | } from '@backstage/backend-common';
22 | import { TaskScheduler } from '@backstage/backend-tasks';
23 | import { Config } from '@backstage/config';
24 | import app from './plugins/app';
25 | import auth from './plugins/auth';
26 | import catalog from './plugins/catalog';
27 | import scaffolder from './plugins/scaffolder';
28 | import proxy from './plugins/proxy';
29 | import techdocs from './plugins/techdocs';
30 | import search from './plugins/search';
31 | import { PluginEnvironment } from './types';
32 | import { ServerPermissionClient } from '@backstage/plugin-permission-node';
33 | import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
34 |
35 | function makeCreateEnv(config: Config) {
36 | const root = getRootLogger();
37 | const reader = UrlReaders.default({ logger: root, config });
38 | const discovery = SingleHostDiscovery.fromConfig(config);
39 | const cacheManager = CacheManager.fromConfig(config);
40 | const databaseManager = DatabaseManager.fromConfig(config, { logger: root });
41 | const tokenManager = ServerTokenManager.noop();
42 | const taskScheduler = TaskScheduler.fromConfig(config);
43 |
44 | const identity = DefaultIdentityClient.create({
45 | discovery,
46 | });
47 | const permissions = ServerPermissionClient.fromConfig(config, {
48 | discovery,
49 | tokenManager,
50 | });
51 |
52 | root.info(`Created UrlReader ${reader}`);
53 |
54 | return (plugin: string): PluginEnvironment => {
55 | const logger = root.child({ type: 'plugin', plugin });
56 | const database = databaseManager.forPlugin(plugin);
57 | const cache = cacheManager.forPlugin(plugin);
58 | const scheduler = taskScheduler.forPlugin(plugin);
59 | return {
60 | logger,
61 | database,
62 | cache,
63 | config,
64 | reader,
65 | discovery,
66 | tokenManager,
67 | scheduler,
68 | permissions,
69 | identity,
70 | };
71 | };
72 | }
73 |
74 | async function main() {
75 | const config = await loadBackendConfig({
76 | argv: process.argv,
77 | logger: getRootLogger(),
78 | });
79 | const createEnv = makeCreateEnv(config);
80 |
81 | const catalogEnv = useHotMemoize(module, () => createEnv('catalog'));
82 | const scaffolderEnv = useHotMemoize(module, () => createEnv('scaffolder'));
83 | const authEnv = useHotMemoize(module, () => createEnv('auth'));
84 | const proxyEnv = useHotMemoize(module, () => createEnv('proxy'));
85 | const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs'));
86 | const searchEnv = useHotMemoize(module, () => createEnv('search'));
87 | const appEnv = useHotMemoize(module, () => createEnv('app'));
88 |
89 | const apiRouter = Router();
90 | apiRouter.use('/catalog', await catalog(catalogEnv));
91 | apiRouter.use('/scaffolder', await scaffolder(scaffolderEnv));
92 | apiRouter.use('/auth', await auth(authEnv));
93 | apiRouter.use('/techdocs', await techdocs(techdocsEnv));
94 | apiRouter.use('/proxy', await proxy(proxyEnv));
95 | apiRouter.use('/search', await search(searchEnv));
96 |
97 | // Add backends ABOVE this line; this 404 handler is the catch-all fallback
98 | apiRouter.use(notFoundHandler());
99 |
100 | const service = createServiceBuilder(module)
101 | .loadConfig(config)
102 | .addRouter('/api', apiRouter)
103 | .addRouter('', await app(appEnv));
104 |
105 | await service.start().catch(err => {
106 | console.log(err);
107 | process.exit(1);
108 | });
109 | }
110 |
111 | module.hot?.accept();
112 | main().catch(error => {
113 | console.error('Backend failed to start up', error);
114 | process.exit(1);
115 | });
116 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/plugins/app.ts:
--------------------------------------------------------------------------------
1 | import { createRouter } from '@backstage/plugin-app-backend';
2 | import { Router } from 'express';
3 | import { PluginEnvironment } from '../types';
4 |
5 | export default async function createPlugin(
6 | env: PluginEnvironment,
7 | ): Promise {
8 | return await createRouter({
9 | logger: env.logger,
10 | config: env.config,
11 | database: env.database,
12 | appPackageName: 'app',
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/plugins/auth.ts:
--------------------------------------------------------------------------------
1 | import {
2 | createRouter,
3 | providers,
4 | defaultAuthProviderFactories,
5 | } from '@backstage/plugin-auth-backend';
6 | import { Router } from 'express';
7 | import { PluginEnvironment } from '../types';
8 |
9 | export default async function createPlugin(
10 | env: PluginEnvironment,
11 | ): Promise {
12 | return await createRouter({
13 | logger: env.logger,
14 | config: env.config,
15 | database: env.database,
16 | discovery: env.discovery,
17 | tokenManager: env.tokenManager,
18 | providerFactories: {
19 | ...defaultAuthProviderFactories,
20 |
21 | // This replaces the default GitHub auth provider with a customized one.
22 | // The `signIn` option enables sign-in for this provider, using the
23 | // identity resolution logic that's provided in the `resolver` callback.
24 | //
25 | // This particular resolver makes all users share a single "guest" identity.
26 | // It should only be used for testing and trying out Backstage.
27 | //
28 | // If you want to use a production ready resolver you can switch to
29 | // the one that is commented out below, it looks up a user entity in the
30 | // catalog using the GitHub username of the authenticated user.
31 | // That resolver requires you to have user entities populated in the catalog,
32 | // for example using https://backstage.io/docs/integrations/github/org
33 | //
34 | // There are other resolvers to choose from, and you can also create
35 | // your own, see the auth documentation for more details:
36 | //
37 | // https://backstage.io/docs/auth/identity-resolver
38 | github: providers.github.create({
39 | signIn: {
40 | resolver(_, ctx) {
41 | const userRef = 'user:default/guest'; // Must be a full entity reference
42 | return ctx.issueToken({
43 | claims: {
44 | sub: userRef, // The user's own identity
45 | ent: [userRef], // A list of identities that the user claims ownership through
46 | },
47 | });
48 | },
49 | // resolver: providers.github.resolvers.usernameMatchingUserEntityName(),
50 | },
51 | }),
52 | },
53 | });
54 | }
55 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/plugins/catalog.ts:
--------------------------------------------------------------------------------
1 | import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
2 | import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend';
3 | import { Router } from 'express';
4 | import { PluginEnvironment } from '../types';
5 |
6 | export default async function createPlugin(
7 | env: PluginEnvironment,
8 | ): Promise {
9 | const builder = await CatalogBuilder.create(env);
10 | builder.addProcessor(new ScaffolderEntitiesProcessor());
11 | const { processingEngine, router } = await builder.build();
12 | await processingEngine.start();
13 | return router;
14 | }
15 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/plugins/proxy.ts:
--------------------------------------------------------------------------------
1 | import { createRouter } from '@backstage/plugin-proxy-backend';
2 | import { Router } from 'express';
3 | import { PluginEnvironment } from '../types';
4 |
5 | export default async function createPlugin(
6 | env: PluginEnvironment,
7 | ): Promise {
8 | return await createRouter({
9 | logger: env.logger,
10 | config: env.config,
11 | discovery: env.discovery,
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/plugins/scaffolder.ts:
--------------------------------------------------------------------------------
1 | import { CatalogClient } from '@backstage/catalog-client';
2 | import { createRouter } from '@backstage/plugin-scaffolder-backend';
3 | import { Router } from 'express';
4 | import type { PluginEnvironment } from '../types';
5 |
6 | export default async function createPlugin(
7 | env: PluginEnvironment,
8 | ): Promise {
9 | const catalogClient = new CatalogClient({
10 | discoveryApi: env.discovery,
11 | });
12 |
13 | return await createRouter({
14 | logger: env.logger,
15 | config: env.config,
16 | database: env.database,
17 | reader: env.reader,
18 | catalogClient,
19 | identity: env.identity,
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/plugins/search.ts:
--------------------------------------------------------------------------------
1 | import { useHotCleanup } from '@backstage/backend-common';
2 | import { createRouter } from '@backstage/plugin-search-backend';
3 | import {
4 | IndexBuilder,
5 | LunrSearchEngine,
6 | } from '@backstage/plugin-search-backend-node';
7 | import { PluginEnvironment } from '../types';
8 | import { DefaultCatalogCollatorFactory } from '@backstage/plugin-catalog-backend';
9 | import { DefaultTechDocsCollatorFactory } from '@backstage/plugin-techdocs-backend';
10 | import { Router } from 'express';
11 |
12 | export default async function createPlugin(
13 | env: PluginEnvironment,
14 | ): Promise {
15 | // Initialize a connection to a search engine.
16 | const searchEngine = new LunrSearchEngine({
17 | logger: env.logger,
18 | });
19 | const indexBuilder = new IndexBuilder({
20 | logger: env.logger,
21 | searchEngine,
22 | });
23 |
24 | const schedule = env.scheduler.createScheduledTaskRunner({
25 | frequency: { minutes: 10 },
26 | timeout: { minutes: 15 },
27 | // A 3 second delay gives the backend server a chance to initialize before
28 | // any collators are executed, which may attempt requests against the API.
29 | initialDelay: { seconds: 3 },
30 | });
31 |
32 | // Collators are responsible for gathering documents known to plugins. This
33 | // collator gathers entities from the software catalog.
34 | indexBuilder.addCollator({
35 | schedule,
36 | factory: DefaultCatalogCollatorFactory.fromConfig(env.config, {
37 | discovery: env.discovery,
38 | tokenManager: env.tokenManager,
39 | }),
40 | });
41 |
42 | // collator gathers entities from techdocs.
43 | indexBuilder.addCollator({
44 | schedule,
45 | factory: DefaultTechDocsCollatorFactory.fromConfig(env.config, {
46 | discovery: env.discovery,
47 | logger: env.logger,
48 | tokenManager: env.tokenManager,
49 | }),
50 | });
51 |
52 | // The scheduler controls when documents are gathered from collators and sent
53 | // to the search engine for indexing.
54 | const { scheduler } = await indexBuilder.build();
55 | scheduler.start();
56 |
57 | useHotCleanup(module, () => scheduler.stop());
58 |
59 | return await createRouter({
60 | engine: indexBuilder.getSearchEngine(),
61 | types: indexBuilder.getDocumentTypes(),
62 | permissions: env.permissions,
63 | config: env.config,
64 | logger: env.logger,
65 | });
66 | }
67 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/plugins/techdocs.ts:
--------------------------------------------------------------------------------
1 | import { DockerContainerRunner } from '@backstage/backend-common';
2 | import {
3 | createRouter,
4 | Generators,
5 | Preparers,
6 | Publisher,
7 | } from '@backstage/plugin-techdocs-backend';
8 | import Docker from 'dockerode';
9 | import { Router } from 'express';
10 | import { PluginEnvironment } from '../types';
11 |
12 | export default async function createPlugin(
13 | env: PluginEnvironment,
14 | ): Promise {
15 | // Preparers are responsible for fetching source files for documentation.
16 | const preparers = await Preparers.fromConfig(env.config, {
17 | logger: env.logger,
18 | reader: env.reader,
19 | });
20 |
21 | // Docker client (conditionally) used by the generators, based on techdocs.generators config.
22 | const dockerClient = new Docker();
23 | const containerRunner = new DockerContainerRunner({ dockerClient });
24 |
25 | // Generators are used for generating documentation sites.
26 | const generators = await Generators.fromConfig(env.config, {
27 | logger: env.logger,
28 | containerRunner,
29 | });
30 |
31 | // Publisher is used for
32 | // 1. Publishing generated files to storage
33 | // 2. Fetching files from storage and passing them to TechDocs frontend.
34 | const publisher = await Publisher.fromConfig(env.config, {
35 | logger: env.logger,
36 | discovery: env.discovery,
37 | });
38 |
39 | // checks if the publisher is working and logs the result
40 | await publisher.getReadiness();
41 |
42 | return await createRouter({
43 | preparers,
44 | generators,
45 | publisher,
46 | logger: env.logger,
47 | config: env.config,
48 | discovery: env.discovery,
49 | cache: env.cache,
50 | });
51 | }
52 |
--------------------------------------------------------------------------------
/src/backstage/packages/backend/src/types.ts:
--------------------------------------------------------------------------------
1 | import { Logger } from 'winston';
2 | import { Config } from '@backstage/config';
3 | import {
4 | PluginCacheManager,
5 | PluginDatabaseManager,
6 | PluginEndpointDiscovery,
7 | TokenManager,
8 | UrlReader,
9 | } from '@backstage/backend-common';
10 | import { PluginTaskScheduler } from '@backstage/backend-tasks';
11 | import { PermissionEvaluator } from '@backstage/plugin-permission-common';
12 | import { IdentityApi } from '@backstage/plugin-auth-node';
13 |
14 | export type PluginEnvironment = {
15 | logger: Logger;
16 | database: PluginDatabaseManager;
17 | cache: PluginCacheManager;
18 | config: Config;
19 | reader: UrlReader;
20 | discovery: PluginEndpointDiscovery;
21 | tokenManager: TokenManager;
22 | scheduler: PluginTaskScheduler;
23 | permissions: PermissionEvaluator;
24 | identity: IdentityApi;
25 | };
26 |
--------------------------------------------------------------------------------
/src/backstage/plugins/README.md:
--------------------------------------------------------------------------------
1 | # The Plugins Folder
2 |
3 | This is where your own plugins and their associated modules live, each in a
4 | separate folder of its own.
5 |
6 | If you want to create a new plugin here, go to your project root directory, run
7 | the command `yarn backstage-cli create`, and follow the on-screen instructions.
8 |
9 | You can also check out existing plugins on [the plugin marketplace](https://backstage.io/plugins)!
10 |
--------------------------------------------------------------------------------
/src/backstage/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@backstage/cli/config/tsconfig.json",
3 | "include": [
4 | "packages/*/src",
5 | "plugins/*/src",
6 | "plugins/*/dev",
7 | "plugins/*/migrations"
8 | ],
9 | "exclude": ["node_modules"],
10 | "compilerOptions": {
11 | "outDir": "dist-types",
12 | "rootDir": "."
13 | }
14 | }
15 |
--------------------------------------------------------------------------------