Templates
Introduction
SCAG works by rendering certain files from Jinja templates, then processing the
resulting directory (containing both user’s application and mentioned
renderings) into system image, measuring and signing it, and last but not least,
packaging everything into Docker (OCI) container. For more complicated
applications (e.g. those that have or had their own Dockerfiles with significant
logic), there might be need to do some postprocessing on docker images after
copying in the build artifacts. In SCAG this is done by overriding templates
that get rendered into configuration files such as Dockerfile or Gramine
manifest. This guide will show how to override the Dockerfile for
python-plain
framework’s helloworld example.
What templates are rendered for a particular app is defined by the framework,
but there are some templates common to all frameworks, like
scag-client.toml
. Some have common rendered path, but are almost always
rendered from framework-specific template (like
.scag/app.manifest.template
rendered from
frameworks/<framework>/app.manifest.template
) or have a common base
template, but frameworks inherit from base template and customise it in some way
(this is the case of .scag/Dockerfile
rendered from
frameworks/<framework>/Dockerfile
, all of which {% extends
'Dockerfile' %}
). If you need to override a template specific to a framework,
unfortunately you need to look into SCAG source to find the exact template name.
SCAG uses Jinja templates. Introduction to this template language is outside of scope for this document, which will only describe concepts needed to explain, how SCAG uses those templates.
Template names, paths and inheritance
In Jinja, templates have names, which usually translate to filenames under some
preconfigured directory (though not always and not directly). An example name
would be Dockerfile
or framework/python-flask/app.manifest.template
. To
override a template, you need to know its name beforehand. In SCAG, default
templates are stored in platlib/graminescaffolding/templates
(on
Debian-derived system it might be
/usr/lib/python3/dist-packages/graminescaffolding/templates
). You should
not change the default templates, your changes will be lost when SCAG is
updated.
To define your own templates, you need to specify your own directory with
templates (usually templates/
subdirectory of your project directory)
and provide its name in application.templates
setting in scag.toml
.
Example from bootstrap contains this line commented, so you probably need to
uncomment it and mkdir
this directory.
Templates which are present in project’s templates/
override completely
templates that have the same name (i.e. are under the same subpath in default
directory). You need to either provide all the template content (even the parts
that you don’t need to change), or you can inherit from one of the default
templates. If you need to inherit template that has the same name, in SCAG you
can add !
character to the template name in {% extends %}
statement:
{% extends '!Dockerfile %}
Just writing {% extends 'Dockerfile' %}
(without !
) is an error, because
it causes recursion in inheritance resolution.
When you are inheriting from existing template, you can override any number of
blocks. Read the original template to see what you can change there. Previous
contents of the block are available as {{ super() }}
.
Choosing the template to override
Here’s a non-exhaustive list of files used by SCAG and templates from which they are rendered (and which we will be overriding):
File
.scag/Dockerfile
is responsible for creating initial (not signed) image. Rendered fromframework/<framework>/Dockerfile
orDockerfile
template (in the root directory of respective templates directory).File
.scag/Dockerfile-final
is responsible for replacingapp.manifest.sgx
andapp.sig
(SIGSTRUCT) inside the container. Rendered fromDockerfile-final
. It is not advisable to override this template.File
.scag/app.manifest.template
, which ends up in the container as/app/app.manifest.template
. Rendered from eitherframeworks/<framework>/app.manifest.template
or (if that template is not provided by framework), thenapp.manifest.template
. This is the Gramine manifest template.Important
You need to know if the framework uses its own manifest template, because if it does, you need to override the framework-specific template. Overriding global template won’t work.
Example
The following file can be placed in
<project_dir>/templates/frameworks/python_plain/Dockerfile
to change the message printed by hello_world.py
script in demo app from
python_plain
:
{% extends '!frameworks/python_plain/Dockerfile' %}
{% block build %}
{{ super() }}
RUN sed -i -e s/world/asdfg/ /app/hello_world.py
{% endblock %}
Template variables
scag.*
Dictionary with system-wide, readonly variables. Those can’t be overridden by user-level variables, nor they should be, as they are e.g., system paths.
scag.builder
Reference to the instance of Builder. Builder has useful attributes: project_dir, scag_dir (also variables, but those are primarily available as globals).
scag.keys_path
Path to directory that ships Gramine and Intel release keys. Used in
setup.sh
hook.scag.magic_dir
Directory that contains all files generated during the build phase. This path is constant, so it can be safely used in a Dockerfile.
sgx.*
Available as
sgx.*
global directory in templates. Used forsgx.sign_args
.
All values in [<framework>]
section in scag.toml
are available as
global variables.
Template filters
shquote
Quotes shell strings (see
shlex.quote()
). Useful in templates. For example, if you need a path passed to a shell command:RUN cp {{ source | shquote }} {{ destination | shquote }}
Template macros
apt_install(package[, package2[, ...]])
Defined in
Dockerfile
template (available if you{% extends 'Dockerfile' %}
).{{ apt_install('pkg1', 'pkg2', ...)
will emitRUN apt-get ...
invocation that will correctly install the set of packages given as arguments.