ЁЯПл The SchoolтА║ЁЯЫдя╕П Platform EngineeringтА║ЁЯЫдя╕П рдзрдбрд╛ 03 тАФ рд╕реЛрдиреЗрд░реА рд╡рд╛рдЯ рдЖрдгрд┐ templates: рддрдпрд╛рд░ рд╡рд░реНрдЧ-рд╕рдВрдЪ
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯЫдя╕П рдзрдбрд╛ 03 тАФ рд╕реЛрдиреЗрд░реА рд╡рд╛рдЯ рдЖрдгрд┐ templates: рддрдпрд╛рд░ рд╡рд░реНрдЧ-рд╕рдВрдЪ

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 03 ┬╖ рдорд╛рдЧреЗ: lesson-02-platform-as-product ┬╖ рдкреБрдвреЗ: lesson-04-service-catalog


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ02, рдЖрдгрд┐ рдкрд╣рд┐рд▓рд╛ рд╕рдВрдЪ: рдПрдХ рд╕реЛрдиреЗрд░реА рд╡рд╛рдЯ (golden path). рдПрдХ scaffolder version рдЕрд╕рд▓реЗрд▓реНрдпрд╛ template рдордзреВрди рдирд╡реА service рдмрдирд╡рддреЛ тАФ code, tests, Dockerfile, pipeline, deployment values, docs рдЖрдгрд┐ catalog рдиреЛрдВрдж тАФ рдЖрдгрд┐ рдЪреБрдХреАрдЪреЗ input рдХрд╕реЗ рджреБрд░реБрд╕реНрдд рдХрд░рд╛рдпрдЪреЗ рддреЗ рд╕рд╛рдВрдЧрдгрд╛рд▒реНрдпрд╛ message рд╕рд╣ рдирд╛рдХрд╛рд░рддреЛ. idp/office.py рдордзрд▓реЗ TEMPLATES рдЖрдгрд┐ Scaffolder, idp/demo.py рдордзрд▓реЗ goldenpath().

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдХрддрд░рд┐рдирд╛рд▓рд╛ рддрд┐рдЪреНрдпрд╛ рдирд╡реНрдпрд╛ рд╡рд┐рдЬреНрдЮрд╛рди рдордВрдбрд│рд╛рд╕рд╛рдареА рдПрдХ рд╡рд░реНрдЧрдЦреЛрд▓реА рд╣рд╡реА рдЖрд╣реЗ. рддреА рд╕реБрд╡рд┐рдзрд╛ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдд рдЬрд╛рддреЗ рдЖрдгрд┐ рдореНрд╣рдгрддреЗ: "рдПрдХ рд╡рд░реНрдЧ-рд╕рдВрдЪ рджреНрдпрд╛. рдирд╛рд╡: exam-results. рдорд╛рд▓рдХ: science."

рджреАрдкрд┐рдХрд╛ рдлрд│реАрд╡рд░реВрди рдПрдХ рд╕рдВрдЪрд╛рдЪреА рдкреЗрдЯреА рдХрд╛рдврддреЗ. ЁЯУж рд▓реЗрдмрд▓рд╡рд░ рд▓рд┐рд╣рд┐рд▓реЗ рдЖрд╣реЗ "python-service, version 3". рдЖрдд 8 рдЧреЛрд╖реНрдЯреА рдЖрд╣реЗрдд, рдЖрдзреАрдЪ рдПрдХрдореЗрдХрд╛рдВрдирд╛ рдЬреЛрдбрд▓реЗрд▓реНрдпрд╛: рдмрд╛рдХреЗ (code), рдзреБрд░рд╛рдЪрд╛ alarm (tests), рдХреБрд▓реВрдк рдЕрд╕рд▓реЗрд▓реЗ рджрд╛рд░ (Dockerfile), рд╡реЗрд│рд╛рдкрддреНрд░рдХрд╛рддрд▓рд╛ рддрд╛рд╕ (pipeline), рдЦреЛрд▓реАрдЪрд╛ рдЖрд░рд╛рдЦрдбрд╛ (deploy values), рд╕реВрдЪрдирд╛ рдлрд▓рдХ (docs) рдЖрдгрд┐ рд╢рд╛рд│реЗрдЪреНрдпрд╛ рдиреЛрдВрджрд╡рд╣реАрд╕рд╛рдареА рдирд╛рд╡рд╛рдЪреЗ рдХрд╛рд░реНрдб (catalog рдиреЛрдВрдж).

рдХрддрд░рд┐рдирд╛рдЪреА рдореИрддреНрд░реАрдг рдЖрдзреА "Exam Results!" рд╣реЗ рдирд╛рд╡ рджреЗрдКрди рдкрд╛рд╣рддреЗ. рджреАрдкрд┐рдХрд╛ рдлрдХреНрдд рдирд╛рд╣реА рдореНрд╣рдгрдд рдирд╛рд╣реА. рддреА рдореНрд╣рдгрддреЗ: "рдирд╛рд╡реЗ рд▓рд╣рд╛рди рдЕрдХреНрд╖рд░рд╛рдВрдд, рдбреЕрд╢ рд▓рд╛рд╡реВрди рдЕрд╕рддрд╛рдд тАФ exam-results рд╡рд╛рдкрд░реВрди рдкрд╛рд╣рд╛." рджреБрд╕рд░реА рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рдЖрдкрд▓рд╛ рд╡рд┐рднрд╛рдЧ "sciense" рдЕрд╕рд╛ рд▓рд┐рд╣рд┐рддреЗ. рджреАрдкрд┐рдХрд╛ рдореНрд╣рдгрддреЗ: "рд╣рд╛ group рдорд▓рд╛ рдорд╛рд╣реАрдд рдирд╛рд╣реА тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдпрд╛рдкреИрдХреА рдПрдЦрд╛рджрд╛ рдореНрд╣рдгрд╛рдпрдЪрд╛ рд╣реЛрддрд╛ рдХрд╛?"

рд╕рдВрдЪ рд╣рд╛ рд╕реЛрдкрд╛ рдорд╛рд░реНрдЧ рдЖрд╣реЗ, рдПрдХрдореЗрд╡ рдорд╛рд░реНрдЧ рдирд╛рд╣реА. рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рдЖрдкрд▓реА рдЦреЛрд▓реА рдкреБрдиреНрд╣рд╛ рд░рдВрдЧрд╡реВ рд╢рдХрддреЗ. рдкрдг рдордЧ рд░рдВрдЧрд╛рдЪреА рдХрд╛рд│рдЬреА рддрд┐рд▓рд╛ рд╕реНрд╡рддрдГрд▓рд╛рдЪ рдШреНрдпрд╛рд╡реА рд▓рд╛рдЧрддреЗ.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    k["ЁЯСйтАНЁЯПл Katrina<br/>template: python-service<br/>name: exam-results<br/>owner: science"] --> c{"тЬЕ checks<br/>name rule ┬╖ name free ┬╖<br/>owner is a group ┬╖<br/>template exists"}
    c -->|"refused"| f["ЁЯТм 'use lowercase and dashes,<br/>e.g. exam-results'"]
    c -->|"ok"| t["ЁЯУж python-service v3"]
    t --> r["ЁЯЧВя╕П 8 files<br/>src ┬╖ tests ┬╖ Dockerfile ┬╖<br/>ci.yml ┬╖ values.yaml ┬╖<br/>docs ┬╖ catalog-info.yaml"]
    r --> g["ЁЯЪА push тЖТ the paved<br/>pipeline runs"]

ЁЯЧ║я╕П рдХрд╛рдврд▓реЗрд▓реА рдЖрдХреГрддреА + рдПрдХ lab: https://school-edh.pages.dev/platform-engineering/lesson-diagrams.html#l03

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг рдирд╡реНрдпрд╛ service рдЪрд╛ рдкрд╣рд┐рд▓рд╛ рддрд╛рд╕ рдмрд░реЗрдЪ рдХрд╛рд╣реА рдард░рд╡рддреЛ: рддрд┐рдЪреНрдпрд╛рдд tests, health check, resource limits, owner, security scan рдЕрд╕рд▓реЗрд▓реА pipeline рдЖрд╣реЗ рдХреА рдирд╛рд╣реА. рдкреНрд░рддреНрдпреЗрдХ team рдХреЛрд▒реНрдпрд╛ рдкрд╛рдирд╛рдкрд╛рд╕реВрди рд╕реБрд░реВ рдЭрд╛рд▓реА рддрд░ рдкреНрд░рддреНрдпреЗрдХ team рд▓рд╛ рдпрд╛рддрд▓рд╛ рд╡реЗрдЧрд│рд╛рдЪ рднрд╛рдЧ рдорд┐рд│рддреЛ. Template рдореБрд│реЗ рдЪрд╛рдВрдЧрд▓реЗ defaults рд╣реАрдЪ рд╕рд░реНрд╡рд╛рдд рд╕реЛрдкреА рдЧреЛрд╖реНрдЯ рдмрдирддреЗ. рддреНрдпрд╛рдореБрд│реЗ services рд╕рд╛рд░рдЦреНрдпрд╛ рджрд┐рд╕рддрд╛рдд, рдореНрд╣рдгрдЬреЗ рдХреЛрдгрддрд╛рд╣реА engineer рдХреЛрдгрддреНрдпрд╛рд╣реА repo рдордзреНрдпреЗ рд░рд╕реНрддрд╛ рд╢реЛрдзреВ рд╢рдХрддреЛ тАФ рдЖрдгрд┐ platform рдирдВрддрд░ рддреНрдпрд╛рддрд▓реНрдпрд╛ рдЕрдиреЗрдХ services рдПрдХрд╛рдЪ рдкрджреНрдзрддреАрдиреЗ рд╕реБрдзрд╛рд░реВ рд╢рдХрддреЛ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

idp/office.py рдордзрд▓реНрдпрд╛ TEMPLATES рдордзреНрдпреЗ рджреЛрди templates рдЖрд╣реЗрдд: python-service (v3, 8 files) рдЖрдгрд┐ static-site (v1, 3 files). Scaffolder(templates, groups, taken) рд▓рд╛ templates, рдЦрд░реЗ groups рдЖрдгрд┐ рдЖрдзреАрдЪ рд╡рд╛рдкрд░рд╛рдд рдЕрд╕рд▓реЗрд▓реА рдирд╛рд╡реЗ рдорд╛рд╣реАрдд рдЕрд╕рддрд╛рдд. check() рд╕рдЧрд│реНрдпрд╛ рдЕрдбрдЪрдгреА рдПрдХрджрдордЪ рдкрд░рдд рджреЗрддреЗ, рдкреНрд░рддреНрдпреЗрдХреАрд╕реЛрдмрдд рджреБрд░реБрд╕реНрддреА; create() {{name}}, {{owner}} рдЖрдгрд┐ {{version}} рднрд░рддреЗ рдЖрдгрд┐ {path: content} рдкрд░рдд рджреЗрддреЗ. рдирд╛рд╡рд╛рдВрд╕рд╛рдареА рдПрдХрдЪ рдирд┐рдпрдо: 3тАУ31 рд▓рд╣рд╛рди рдЕрдХреНрд╖рд░реЗ, рдЕрдВрдХ рдХрд┐рдВрд╡рд╛ рдбреЕрд╢, рд╕реБрд░реБрд╡рд╛рдд рдЕрдХреНрд╖рд░рд╛рдиреЗ. рддрдпрд╛рд░ рдЭрд╛рд▓реЗрд▓реНрдпрд╛ ci.yml рдордзреНрдпреЗ pipeline рдирд╕рддреЗ тАФ рддреА рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреА рд╕рд╛рдорд╛рдпрд┐рдХ pipeline рдмреЛрд▓рд╛рд╡рддреЗ (рдзрдбрд╛ 07).

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 idp/demo.py goldenpath
python3 - <<'EOF'
import sys; sys.path.insert(0, "idp"); from office import Scaffolder, TEMPLATES
sc = Scaffolder(TEMPLATES, ["science", "maths", "library"], taken={"timetable"})
for args in (("static-site", "open-day", "maths"), ("static-site", "open-day", "maths"), ("python-service", "x", "library")):
    ok, out = sc.create(*args)
    print(args, "тЖТ", sorted(out) if ok else out)
ok, files = sc.create("python-service", "book-returns", "library")
print(files["README.md"].splitlines()[1])
print(files[".github/workflows/ci.yml"].splitlines()[-1].strip())
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

goldenpath рд╣реЗ рдЫрд╛рдкрддреЗ (рдЖрдзреА рдирдХрд╛рд░, рдордЧ рд╕рдВрдЪ):

тФАтФА create ('python-service', 'Exam Results!', 'science') тЖТ refused:
   тАв name 'Exam Results!' тАФ use 3тАУ31 lowercase letters, digits or dashes, starting with a letter, e.g. 'exam-results'
тФАтФА create ('python-service', 'timetable', 'sciense') тЖТ refused:
   тАв name 'timetable' is taken тАФ pick another, or ask its owner in the catalog
   тАв owner 'sciense' is not a known group тАФ choose one of: facilities-office, library, maths, science
тФАтФА Katrina creates 'exam-results' from python-service v3 тЖТ 8 files, ready to push:

рддреБрдордЪрд╛ snippet рд╣реЗ рдЫрд╛рдкрддреЛ:

('static-site', 'open-day', 'maths') тЖТ ['README.md', 'catalog-info.yaml', 'site/index.html']
('static-site', 'open-day', 'maths') тЖТ ["name 'open-day' is taken тАФ pick another, or ask its owner in the catalog"]
('python-service', 'x', 'library') тЖТ ["name 'x' тАФ use 3тАУ31 lowercase letters, digits or dashes, starting with a letter, e.g. 'my-new-service'"]
Owned by library. Made from the python-service template v3.
uses: school/paved-road/.github/workflows/python.yml@v2

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдПрдХрдЪ рд╡рд┐рдирдВрддреА рджреЛрдирджрд╛ рдХреЗрд▓реА рддрд░ рджреЛрди рдЙрддреНрддрд░реЗ рдорд┐рд│рддрд╛рдд: рдкрд╣рд┐рд▓реЗ open-day рдмрдирддреЗ, рджреБрд╕рд░реЗ рдирд╛рдХрд╛рд░рд▓реЗ рдЬрд╛рддреЗ рдХрд╛рд░рдг рддреЗ рдирд╛рд╡ рдЖрддрд╛ рдШреЗрддрд▓реЗрд▓реЗ рдЖрд╣реЗ тАФ scaffolder рд▓рдХреНрд╖рд╛рдд рдареЗрд╡рддреЛ. рдПрдХрд╛ рд╡рд┐рдирдВрддреАрддрд▓реНрдпрд╛ рджреЛрди рдЪреБрдХрд╛рдВрдирд╛ рджреЛрди messages рдПрдХрджрдордЪ рдорд┐рд│рддрд╛рдд, рдореНрд╣рдгрдЬреЗ рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рджреЛрдиреНрд╣реА рдПрдХрд╛рдЪ рд╡реЗрд│реА рджреБрд░реБрд╕реНрдд рдХрд░рддреЗ. рдЖрдгрд┐ рдирд╡реА service pipeline рдЪреА copy рдХрд░рдд рдирд╛рд╣реА; рддреА @v2 рд╡рд░рдЪреНрдпрд╛ рдХрд╛рд░реНрдпрд╛рд▓рдпрд╛рдЪреНрдпрд╛ рд╕рд╛рдорд╛рдпрд┐рдХ pipeline рдХрдбреЗ рдирд┐рд░реНрджреЗрд╢ рдХрд░рддреЗ, рдореНрд╣рдгрдЬреЗ рддреНрдпрд╛ pipeline рдордзрд▓реА рдирдВрддрд░рдЪреА рджреБрд░реБрд╕реНрддреА рддрд┐рдЪреНрдпрд╛рдкрд░реНрдпрдВрдд рдкреЛрд╣реЛрдЪреВ рд╢рдХрддреЗ (рдзрдбрд╛ 07). рдкреНрд░рддреНрдпреЗрдХ file рд╕рд╛рдВрдЧрддреЗ рдХреА рддреА рдХреЛрдгрддреНрдпрд╛ template рдЖрдгрд┐ version рдиреЗ рдмрдирд╡рд▓реА.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд

рдЦрд▒реНрдпрд╛ Backstage instance рд╡рд░рдЪрд╛ Backstage Software Template рд╣реА рдПрдХ YAML entity рдЕрд╕рддреЗ. рдПрдХ рдЫреЛрдЯрд╛ template тАФ рджреЛрди рдкреНрд░рд╢реНрди рд╡рд┐рдЪрд╛рд░рд╛, рд╕рд╛рдВрдЧрд╛рдбрд╛ рднрд░рд╛, repo publish рдХрд░рд╛, catalog рдордзреНрдпреЗ рдиреЛрдВрджрд╡рд╛:

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: python-service
  title: Python service (golden path)
spec:
  owner: facilities-office
  type: service
  parameters:
    - title: About the service
      required: [name, owner]
      properties:
        name:
          type: string
          pattern: '^[a-z][a-z0-9-]{2,30}$'
        owner:
          type: string
          ui:field: OwnerPicker
  steps:
    - id: fetch
      action: fetch:template
      input: {url: ./skeleton, values: {name: '${{ parameters.name }}', owner: '${{ parameters.owner }}'}}
    - id: publish
      action: publish:github
      input: {repoUrl: 'github.com?owner=school&repo=${{ parameters.name }}'}
    - id: register
      action: catalog:register
      input: {repoContentsUrl: '${{ steps.publish.output.repoContentsUrl }}', catalogInfoPath: /catalog-info.yaml}

Backstage рдирд╕реЗрд▓ рддрд░ cookiecutter day-1 рдЪрд╛ рднрд╛рдЧ local рд╡рд░ рдХрд░рддреЛ:

pip install cookiecutter
cookiecutter gh:school/python-service-template   # asks for name and owner, writes the skeleton

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: рдХрд┐рддреА рдирд╡реНрдпрд╛ services template рдордзреВрди рд╕реБрд░реВ рд╣реЛрддрд╛рдд рддреЗ рдореЛрдЬрд╛. teams рддреНрдпрд╛рдРрд╡рдЬреА рдЬреБрдиреНрдпрд╛ repo рдЪреА copy рдХрд░рдд рдЕрд╕рддреАрд▓ рддрд░ template рдЕрдЬреВрди рд╕реЛрдкреА рд╡рд╛рдЯ рдмрдирд▓реЗрд▓рд╛ рдирд╛рд╣реА тАФ рдХрд╛ рддреЗ рд╢реЛрдзрд╛ (рдзрдбрд╛ 02).

тПня╕П рдкреБрдвреЗ

рдкреНрд░рддреНрдпреЗрдХ рд╕рдВрдЪрд╛рдиреЗ рдПрдХ catalog-info.yaml рдмрдирд╡рд▓реА. рдкреБрдвреЗ: рддреНрдпрд╛ рд╡рд╛рдЪрдгрд╛рд░реА рдиреЛрдВрджрд╡рд╣реА тАФ рдХрд╢рд╛рдЪрд╛ рдорд╛рд▓рдХ рдХреЛрдг, рдХрд╛рдп рдХрд╢рд╛рд╡рд░ рдЕрд╡рд▓рдВрдмреВрди рдЖрд╣реЗ, рдЖрдгрд┐ рдХреЛрдгрддреНрдпрд╛ рд╡рд░реНрдЧрдЦреЛрд▓реНрдпрд╛рдВрдЪреА рдХреЛрдгреАрдЪ рдХрд╛рд│рдЬреА рдШреЗрдд рдирд╛рд╣реА.

git checkout lesson-04-service-catalog

ЁЯЫдя╕П Lesson 03 тАФ Golden paths & templates: a ready classroom kit

ЁЯУН You are here: Lesson 03 of 12 ┬╖ Previous: lesson-02-platform-as-product ┬╖ Next: lesson-04-service-catalog


ЁЯУж What's in this branch

Lessons 01тАУ02, plus the first kit: a golden path. A scaffolder creates a new service from a versioned template тАФ code, tests, Dockerfile, pipeline, deployment values, docs and a catalog entry тАФ and refuses bad input with a message that says how to fix it. TEMPLATES and Scaffolder in idp/office.py, goldenpath() in idp/demo.py.

ЁЯзТ Explain like I'm 5

Katrina needs a classroom for her new science club. She goes to the facilities office and says: "One classroom kit, please. Name: exam-results. Owner: science."

Dipika takes a kit box from the shelf. ЁЯУж The label says "python-service, version 3". Inside are 8 things, already fitted together: desks (the code), a smoke alarm (the tests), a door with a lock (the Dockerfile), a timetable slot (the pipeline), a room plan (the deploy values), a notice board (the docs) and a name card for the school register (the catalog entry).

Katrina's friend tries first with the name "Exam Results!". Dipika does not just say no. She says: "Names are lowercase with dashes тАФ try exam-results." Another teacher writes her department as "sciense". Dipika says: "I don't know that group тАФ did you mean one of these?"

The kit is the easy way, not the only way. A teacher may repaint her room. But then she looks after the paint herself.

ЁЯЧ║я╕П Diagram

flowchart LR
    k["ЁЯСйтАНЁЯПл Katrina<br/>template: python-service<br/>name: exam-results<br/>owner: science"] --> c{"тЬЕ checks<br/>name rule ┬╖ name free ┬╖<br/>owner is a group ┬╖<br/>template exists"}
    c -->|"refused"| f["ЁЯТм 'use lowercase and dashes,<br/>e.g. exam-results'"]
    c -->|"ok"| t["ЁЯУж python-service v3"]
    t --> r["ЁЯЧВя╕П 8 files<br/>src ┬╖ tests ┬╖ Dockerfile ┬╖<br/>ci.yml ┬╖ values.yaml ┬╖<br/>docs ┬╖ catalog-info.yaml"]
    r --> g["ЁЯЪА push тЖТ the paved<br/>pipeline runs"]

ЁЯЧ║я╕П Drawn version + a lab: https://school-edh.pages.dev/platform-engineering/lesson-diagrams.html#l03

тЭУ What

ЁЯдФ Why

Because the first hour of a new service decides a lot: whether it has tests, a health check, resource limits, an owner, a pipeline with a security scan. If each team starts from a blank page, each team gets a different subset of those. A template makes the good defaults the easiest thing to do. It also makes services look alike, so any engineer can find their way around any repo тАФ and the platform can improve many of them the same way later.

ЁЯФз How (in this repo)

TEMPLATES in idp/office.py holds two templates: python-service (v3, 8 files) and static-site (v1, 3 files). Scaffolder(templates, groups, taken) knows the templates, the real groups and the names already in use. check() returns every problem at once, each with a fix; create() fills {{name}}, {{owner}} and {{version}} and returns {path: content}. Names follow one rule: 3тАУ31 lowercase letters, digits or dashes, starting with a letter. The generated ci.yml does not contain a pipeline тАФ it calls the office's shared one (lesson 07).

ЁЯзк Try it

python3 idp/demo.py goldenpath
python3 - <<'EOF'
import sys; sys.path.insert(0, "idp"); from office import Scaffolder, TEMPLATES
sc = Scaffolder(TEMPLATES, ["science", "maths", "library"], taken={"timetable"})
for args in (("static-site", "open-day", "maths"), ("static-site", "open-day", "maths"), ("python-service", "x", "library")):
    ok, out = sc.create(*args)
    print(args, "тЖТ", sorted(out) if ok else out)
ok, files = sc.create("python-service", "book-returns", "library")
print(files["README.md"].splitlines()[1])
print(files[".github/workflows/ci.yml"].splitlines()[-1].strip())
EOF

тЬЕ Verify тАФ what you should see

goldenpath prints (first the refusals, then the kit):

тФАтФА create ('python-service', 'Exam Results!', 'science') тЖТ refused:
   тАв name 'Exam Results!' тАФ use 3тАУ31 lowercase letters, digits or dashes, starting with a letter, e.g. 'exam-results'
тФАтФА create ('python-service', 'timetable', 'sciense') тЖТ refused:
   тАв name 'timetable' is taken тАФ pick another, or ask its owner in the catalog
   тАв owner 'sciense' is not a known group тАФ choose one of: facilities-office, library, maths, science
тФАтФА Katrina creates 'exam-results' from python-service v3 тЖТ 8 files, ready to push:

Your snippet prints:

('static-site', 'open-day', 'maths') тЖТ ['README.md', 'catalog-info.yaml', 'site/index.html']
('static-site', 'open-day', 'maths') тЖТ ["name 'open-day' is taken тАФ pick another, or ask its owner in the catalog"]
('python-service', 'x', 'library') тЖТ ["name 'x' тАФ use 3тАУ31 lowercase letters, digits or dashes, starting with a letter, e.g. 'my-new-service'"]
Owned by library. Made from the python-service template v3.
uses: school/paved-road/.github/workflows/python.yml@v2

ЁЯПБ What you just proved

The same request twice gives two answers: the first open-day is created, the second is refused because the name is now taken тАФ the scaffolder remembers. Two mistakes in one request get two messages at once, so the teacher fixes both in one go. And the new service does not copy a pipeline; it points to the office's shared pipeline at @v2, so a later fix to that pipeline can reach it (lesson 07). Every file says which template and version made it.

тЪая╕П Common mistakes

ЁЯПн In production

A Backstage Software Template, on a real Backstage instance, is a YAML entity. A small one тАФ ask two questions, fill a skeleton, publish a repo, register it in the catalog:

apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: python-service
  title: Python service (golden path)
spec:
  owner: facilities-office
  type: service
  parameters:
    - title: About the service
      required: [name, owner]
      properties:
        name:
          type: string
          pattern: '^[a-z][a-z0-9-]{2,30}$'
        owner:
          type: string
          ui:field: OwnerPicker
  steps:
    - id: fetch
      action: fetch:template
      input: {url: ./skeleton, values: {name: '${{ parameters.name }}', owner: '${{ parameters.owner }}'}}
    - id: publish
      action: publish:github
      input: {repoUrl: 'github.com?owner=school&repo=${{ parameters.name }}'}
    - id: register
      action: catalog:register
      input: {repoContentsUrl: '${{ steps.publish.output.repoContentsUrl }}', catalogInfoPath: /catalog-info.yaml}

Without Backstage, cookiecutter does the day-1 part locally:

pip install cookiecutter
cookiecutter gh:school/python-service-template   # asks for name and owner, writes the skeleton

ЁЯПн Why this matters in production: measure how many new services start from the template. If teams copy an old repo instead, the template is not the easy path yet тАФ find out why (lesson 02).

тПня╕П Next

Every kit made a catalog-info.yaml. Next: the register that reads them тАФ who owns what, what depends on what, and which classrooms nobody looks after.

git checkout lesson-04-service-catalog
тЖР Previousplatform as productNext тЖТservice catalog

This page is the lesson's README from the lesson-03-golden-paths branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.