본문 바로가기
WIKI 기술 지식 베이스

템플릿 필터

원문 보기 위키 갱신

템플릿 필터 (Templating Filters)

Backstage 템플릿은 Nunjucks 구문의 집중된 하위 집합을 지원하는 안전한 템플릿 엔진인 Nunjitsu로 구동됩니다.

출처: 문서

본문

Backstage 템플릿은 Nunjucks 구문의 집중된 하위 집합을 지원하는 안전한 템플릿 엔진인 Nunjitsu로 구동됩니다. 일반 구문 참조로는 Nunjucks 템플릿 문서를 사용하고, 지원되는 기능에 대해서는 Nunjitsu 호환성 가이드를 확인하세요.

필터는 Scaffolder 템플릿을 렌더링하는 데 중요한 메커니즘으로, 익숙한 파이프 방식으로 값을 변형하는 수단을 제공합니다. 템플릿 필터는 Scaffolder 템플릿에서 데이터를 변형하고, 특정 정보를 추출하며, 다양한 연산을 수행하는 데 도움을 주는 함수입니다.

내장 (Built-in)

Backstage는 다음과 같은 "내장" 템플릿 필터 집합을 기본 제공합니다(자신만의 커스텀 필터를 만들려면 이하의 Custom Filter 섹션을 참조하세요).

parseRepoUrl

parseRepoUrl 필터는 저장소 URL을 구성 요소들로 파싱합니다. owner, 저장소 이름(repo) 등이 있습니다.

사용 예:

- id: log  name: Parse Repo URL  action: debug:log  input:    message: ${{ parameters.repoUrl | parseRepoUrl }}
  • 입력: github.com?repo=backstage&owner=backstage
  • 출력: "RepoSpec" (parseRepoUrl 참조)

parseEntityRef

parseEntityRef 필터는 kind, namespace, name 같은 엔티티 참조의 서로 다른 부분을 추출할 수 있게 해 줍니다.

사용 예

  • 컨텍스트 없이
- id: log  name: Parse Entity Reference  action: debug:log  input:    message: ${{ parameters.owner | parseEntityRef }}
  • 입력: group:techdocs

  • 출력: CompoundEntityRef

  • 컨텍스트와 함께

- id: log  name: Parse Entity Reference  action: debug:log  input:    message: ${{ parameters.owner | parseEntityRef({ defaultKind:"group", defaultNamespace:"another-namespace" }) }}
  • 입력: techdocs
  • 출력: CompoundEntityRef

pick

pick 필터는 객체에서 특정 속성(예: kind, namespace, name)을 선택할 수 있게 해 줍니다.

사용 예

- id: log  name: Pick  action: debug:log  input:    message: ${{ parameters.owner | parseEntityRef | pick('name') }}
  • 입력: { kind: 'Group', namespace: 'default', name: 'techdocs' }
  • 출력: techdocs

projectSlug

projectSlug 필터는 저장소 URL에서 프로젝트 슬러그를 생성합니다.

사용 예

- id: log  name: Project Slug  action: debug:log  input:    message: ${{ parameters.repoUrl | projectSlug }}
  • 입력: github.com?repo=backstage&owner=backstage
  • 출력: backstage/backstage

템플릿 전역 (Templating Globals)

강력한 필터링 기능 외에도, Nunjitsu는 템플릿 표현식 컨텍스트에서 지정된 전역적으로 접근 가능한 참조에 접근할 수 있게 해 줍니다. Backstage는 이를 스캐폴더 백엔드 플러그인을 통해 전파하며, 곧 실제로 보게 될 것입니다.

템플릿 환경 커스터마이즈

커스텀 플러그인은 필터, 전역 함수, 전역 값의 어떤 조합이든 될 수 있는 자신만의 템플릿 확장을 설치하는 것을 가능하게 합니다. 새 백엔드에서는 이를 위해 스캐폴더 플러그인 모듈을 사용할 것입니다. 나중에 이전 백엔드로 비슷한 접근 방식을 보여드리겠습니다.

Backstage CLI로 템플릿 확장 모듈 생성 간소화

Backstage에서 "템플릿 환경 커스터마이즈" 모듈 생성은 Backstage CLI를 사용해 가속화할 수 있습니다.

먼저 yarn backstage-cli new 명령을 사용해 스캐폴더 모듈을 생성하세요. 이 명령은 필요한 보일러플레이트 코드를 설정해 부드러운 시작을 제공합니다.

$ yarn backstage-cli new? What do you want to create?  frontend-plugin - A new frontend plugin  backend-plugin - A new backend plugin❯ backend-plugin-module - A new backend module that extends an existing backend plugin  plugin-web-library - A new web library plugin package  plugin-node-library - A new Node.js library plugin package  plugin-common-library - A new isomorphic common plugin package  web-library - A library package, exporting shared functionality for web environments

프롬프트가 나타나면 화살표 키를 사용해 backend-plugin-module을 생성하는 옵션을 선택하세요. Scaffolder 백엔드를 확장하고 싶으므로, 확장할 플러그인의 ID를 묻는 프롬프트에 scaffolder를 입력하세요. 다음으로 모듈의 ID(이름)를 입력하세요. 이것은 scaffolder-backend-module- 접두사에 추가됩니다. 그러면 CLI가 필요한 파일과 디렉터리 구조를 생성합니다. 예를 들어:

? Enter the ID of the plugin [required] scaffolder? Enter the ID of the module [required] foo-bar  templating    plugins/scaffolder-backend-module-foo-bar ✔  backend       adding @internal/plugin-scaffolder-backend-module-foo-bar ✔  executing     yarn install ✔  executing     yarn lint --fix ✔🎉  Successfully created backend-plugin-module

디렉터리 구조

plugins├── README.md├── scaffolder-backend-module-foo-bar│   ├── package.json│   ├── README.md│   └── src│       ├── index.ts│       └── module.ts

모듈 작성

CLI가 새 스캐폴더 모듈을 위한 필수 구조를 생성하면, 이제 템플릿 확장을 구현할 차례입니다. 여기서는 지원되는 각 확장 타입을 만드는 방법을 보여드리겠습니다.

src/module.ts가 핵심이 일어나는 곳입니다. 먼저 관련 (alpha 단계) API 확장 지점을 활용할 준비를 하기 위해 아래 import를 src/module.ts에 추가합니다.

import { scaffolderTemplatingExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha';

생성된 코드를 보면, 모든 것이 컨텍스트를 확립하기 위한 최소한의 메타데이터를 제공한 뒤, 받은 BackendModuleRegistrationPoints 인자에 대해 차례로 registerInit을 호출하는 것이 유일한 책임인 register 콜백을 지정하는 createBackendModule 호출에 기반한다는 것을 알 수 있습니다. 지정된 init 함수가 scaffolderTemplatingExtensionPoint를 사용할 수 있도록 이 호출을 수정하세요.

register(reg) {    reg.registerInit({      deps: {        ...,        templating: scaffolderTemplatingExtensionPoint,      },      async init({        ...,        templating        }) {        ...      };    });  };

이제 스캐폴더 템플릿 엔진을 확장할 준비가 되었습니다. 우리의 목적을 위해 여기서는 모든 것을 module.ts에 넣겠습니다. 실제 플러그인 모듈의 구성은 여러분의 판단에 맡깁니다.

커스텀 필터

이 인위적인 예시에서 들어오는 문자열 값이 주어진 하위 문자열을 (최소한) 지정된 횟수만큼 포함하는지 테스트하는 필터를 추가합니다. init 콜백에 코드를 추가해 이를 쉽게 정의할 수 있습니다.

async init({  ...,  templating,}) {  ...  templating.addTemplateFilters({    containsOccurrences: (arg: string, substring: string, times: number) => {      let pos = 0;      let count = 0;      while (pos < arg.length) {        pos = arg.indexOf(substring, pos);        if (pos < 0) {          break;        }        count++;      }      return count === times;    },  });},

이것은 등록할 명명된 템플릿 필터 구현의 TypeScript Record라는 최소한의 것을 보여줍니다. 그러나 대체 구조를 채택하면 추가 메타데이터로 필터를 문서화할 수 있습니다. 이 능력을 활용하려면 먼저 새 import를 추가합니다.

import { createTemplateFilter } from '@backstage/plugin-scaffolder-node/alpha';

그런 다음 init 구현을 객체/레코드 대신 배열을 지정하도록 갱신합니다.

async init({  ...,  templating,}) {  ...  templating.addTemplateFilters([    createTemplateFilter({      id: 'containsOccurrences',      description: 'determine whether filter input contains a substring N times',      filter: (arg: string, substring: string, times: number) => {        let pos = 0;        let count = 0;        while (pos < arg.length) {          pos = arg.indexOf(substring, pos);          if (pos < 0) {            break;          }          count++;        }        return count === times;      },    }),  ]);},

이렇게 하면 필터에 description을 추가했는데, 이는 템플릿 작성자가 필터의 목적을 이해하는 데 도움을 줍니다.

스키마

필터 문서를 더욱 강화하기 위해, Zod 스키마 선언 라이브러리에 대한 콜백을 사용해 schema를 지정합니다.

createTemplateFilter({      id: 'containsOccurrences',      description: 'determine whether filter input contains a substring N times',      schema: z =>        z.function(          z.tuple([            z.string().describe('input'),            z.string().describe('substring whose occurrences to find'),            z.number().describe('number of occurrences to check for'),          ]),          z.boolean(),        ),      ...,    }),

필터는 사실 함수이므로, 그 스키마는 우리의 스키마 콜백에 전달된 매개변수에 대해 Zod 함수 스키마를 생성해 정의됩니다. 필터 함수는 적어도 하나의 인자를 가져야 합니다. 이 예시에서는 추가 인자 두 개가 있습니다. 하지만 필터의 구현 함수를 수정해 times를 선택적으로 만든다면 어떨까요? 코드:

createTemplateFilter({      id: 'containsOccurrences',      ...,      filter: (arg: string, substring: string, times?: number) => {        if (times === undefined) {          // note that, in real life, calling a global function directly from the template would suffice rather than implementing a filter:          return arg.includes(substring);        }        // original implementation follows        ...      },    }),

이 경우 schema를 수정해야 합니다.

createTemplateFilter({      ...,      schema: z =>        z.function(          z.tuple([            z.string().describe('input'),            z.string().describe('substring whose occurrences to find'),            z              .number()              .describe('number of occurrences to check for')              .optional(),          ]),          z.boolean(),        ),      ...,    }),

필터 예시 문서

필터 문서는 다음과 같이 지정하는 예시로 혜택을 볼 수 있습니다.

createTemplateFilter({      ...,      examples: [        {          description: 'Basic Usage',          example: `\- name: Contains Occurrences  action: debug:log  input:    message: \${{ parameters.projectName | containsOccurrences('-', 2) }}          `,          notes: `\- **Input**: \`foo-bar-baz\`- **Output**: \`true\`      `,        },        {          description: 'Omitting Optional Parameter',          example: `\- name: Contains baz  action: debug:log  input:    message: \${{ parameters.projectName | containsOccurrences('baz') | dump }}          `,          notes: `\- **Input**: \`foo-bar\`- **Output**: \`false\`        `,        },      ],    }),

커스텀 전역 함수

템플릿이 필터로 적절하게 모델링되지 않은 함수에서 생성된 값에 접근해야 하는 경우, Nunjitsu는 전역 함수의 직접 호출을 지원합니다. 예를 들어 init에 다음을 추가할 수 있습니다.

async init({  ...,  templating,}) {  ...  templating.addTemplateGlobals({    now: () => new Date().toISOString(),  });},

여기서 전역적으로 사용 가능한 함수를 사용해 타임스탬프를 얻는 간단한 메커니즘을 구현했습니다 (JSON 호환 값 또는 undefined만 전달할 수 있으므로 날짜/시간을 ISO 문자열로 모델링하기로 선택했음에 주목).

다시 한번 전역 함수를 자체 문서화하게 할 수 있는 옵션이 있습니다. import:

import {  ...,  createTemplateGlobalFunction,} from '@backstage/plugin-scaffolder-node/alpha';

그런 다음 수정:

...  templating.addTemplateGlobals([    createTemplateGlobalFunction({      id: 'now',      description:        'obtain an ISO representation of the current date and time',      fn: () => new Date().toISOString(),    }),  ]);

스키마

전역 함수 스키마를 선언하는 것은 템플릿 필터의 스키마 선언과 상당히 비슷합니다.

createTemplateGlobal({      ...,      schema: z => z.function().args().returns(z.string()),      ...,    }),

전역 함수 예시 문서

다시 한번, 이것은 필터 예시와 같은 방식으로 작동합니다.

createTemplateGlobal({      ...,      examples: [        {          description: 'Obtain the current date/time',          example: `\- name: Log Timestamp  action: debug:log  input:    message: Current date/time: \${{ now() }}          `,          // optional `notes` omitted from this example        },      ],      ...,    }),

커스텀 전역 값

대안으로, 템플릿에 단순한 JSON 값에 대한 접근을 제공해야 할 수도 있습니다. 다음과 같이 등록할 수 있습니다.

async init({  ...,  templating,}) {  ...  templating.addTemplateGlobals({    ...,    preferredMetasyntacticIdentifier: 'foo',  });},

또는 문서화하는 형태:

async init({  ...,  templating,}) {  ...  templating.addTemplateGlobals([    ...,    createTemplateGlobalValue({      id: 'preferredMetasyntacticIdentifier',      value: 'foo',      description:        'This description is as contrived as the global value it documents',    }),  ]);},

이전 백엔드 시스템으로 템플릿 확장 등록

원래 Backstage 백엔드의 사용자는 스캐폴더 백엔드 플러그인의 createRouter 함수(관례상 packages/backend/src/plugins/scaffolder.ts에서 호출)에 옵션을 지정해 템플릿 확장을 등록할 수 있습니다.

  • additionalTemplateFilters — 다음 중 하나:
    • 필터 이름을 구현 함수에 매핑하는 객체, 또는
    • 유틸리티 함수 createTemplateFilter가 반환하는 문서화된 템플릿 필터의 배열
  • additionalTemplateGlobals — 다음 중 하나:
    • 전역 이름을 값이나 함수에 매핑하는 객체, 또는
    • 유틸리티 함수 createTemplateGlobalFunction과 createTemplateGlobalValue가 반환하는 문서화된 전역 함수와 값의 배열

더 알아보기 (Learn more)