/
plugin.py
229 lines (180 loc) · 7.85 KB
/
plugin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
from __future__ import annotations
import os
from glob import glob
import click
import importlib_resources
from tutor import hooks
from .__about__ import __version__
########################################
# CONFIGURATION
########################################
hooks.Filters.CONFIG_DEFAULTS.add_items(
[
# Add your new settings that have default values here.
# Each new setting is a pair: (setting_name, default_value).
# Prefix your setting names with '{{ cookiecutter.plugin_name|upper|replace('-', '_') }}_'.
("{{ cookiecutter.plugin_name|upper|replace('-', '_') }}_VERSION", __version__),
]
)
hooks.Filters.CONFIG_UNIQUE.add_items(
[
# Add settings that don't have a reasonable default for all users here.
# For instance: passwords, secret keys, etc.
# Each new setting is a pair: (setting_name, unique_generated_value).
# Prefix your setting names with '{{ cookiecutter.plugin_name|upper|replace('-', '_') }}_'.
# For example:
### ("{{ cookiecutter.plugin_name|upper|replace('-', '_') }}_SECRET_KEY", "{{ '{{' }} 24|random_string {{ '}}' }}"),
]
)
hooks.Filters.CONFIG_OVERRIDES.add_items(
[
# Danger zone!
# Add values to override settings from Tutor core or other plugins here.
# Each override is a pair: (setting_name, new_value). For example:
### ("PLATFORM_NAME", "My platform"),
]
)
########################################
# INITIALIZATION TASKS
########################################
# To add a custom initialization task, create a bash script template under:
# {{ cookiecutter.module_name }}/templates/{{ cookiecutter.plugin_name }}/tasks/
# and then add it to the MY_INIT_TASKS list. Each task is in the format:
# ("<service>", ("<path>", "<to>", "<script>", "<template>"))
MY_INIT_TASKS: list[tuple[str, tuple[str, ...]]] = [
# For example, to add LMS initialization steps, you could add the script template at:
# {{ cookiecutter.module_name }}/templates/{{ cookiecutter.plugin_name }}/tasks/lms/init.sh
# And then add the line:
### ("lms", ("{{ cookiecutter.plugin_name }}", "tasks", "lms", "init.sh")),
]
# For each task added to MY_INIT_TASKS, we load the task template
# and add it to the CLI_DO_INIT_TASKS filter, which tells Tutor to
# run it as part of the `init` job.
for service, template_path in MY_INIT_TASKS:
full_path: str = str(
importlib_resources.files("{{ cookiecutter.module_name }}")
/ os.path.join("templates", *template_path)
)
with open(full_path, encoding="utf-8") as init_task_file:
init_task: str = init_task_file.read()
hooks.Filters.CLI_DO_INIT_TASKS.add_item((service, init_task))
########################################
# DOCKER IMAGE MANAGEMENT
########################################
# Images to be built by `tutor images build`.
# Each item is a quadruple in the form:
# ("<tutor_image_name>", ("path", "to", "build", "dir"), "<docker_image_tag>", "<build_args>")
hooks.Filters.IMAGES_BUILD.add_items(
[
# To build `myimage` with `tutor images build myimage`,
# you would add a Dockerfile to templates/{{ cookiecutter.plugin_name }}/build/myimage,
# and then write:
### (
### "myimage",
### ("plugins", "{{ cookiecutter.plugin_name }}", "build", "myimage"),
### "docker.io/myimage:{{ '{{' }} {{ cookiecutter.plugin_name|upper|replace('-', '_') }}_VERSION {{ '}}' }}",
### (),
### ),
]
)
# Images to be pulled as part of `tutor images pull`.
# Each item is a pair in the form:
# ("<tutor_image_name>", "<docker_image_tag>")
hooks.Filters.IMAGES_PULL.add_items(
[
# To pull `myimage` with `tutor images pull myimage`, you would write:
### (
### "myimage",
### "docker.io/myimage:{{ '{{' }} {{ cookiecutter.plugin_name|upper|replace('-', '_') }}_VERSION {{ '}}' }}",
### ),
]
)
# Images to be pushed as part of `tutor images push`.
# Each item is a pair in the form:
# ("<tutor_image_name>", "<docker_image_tag>")
hooks.Filters.IMAGES_PUSH.add_items(
[
# To push `myimage` with `tutor images push myimage`, you would write:
### (
### "myimage",
### "docker.io/myimage:{{ '{{' }} {{ cookiecutter.plugin_name|upper|replace('-', '_') }}_VERSION {{ '}}' }}",
### ),
]
)
########################################
# TEMPLATE RENDERING
# (It is safe & recommended to leave
# this section as-is :)
########################################
hooks.Filters.ENV_TEMPLATE_ROOTS.add_items(
# Root paths for template files, relative to the project root.
[
str(importlib_resources.files("{{ cookiecutter.module_name }}") / "templates"),
]
)
hooks.Filters.ENV_TEMPLATE_TARGETS.add_items(
# For each pair (source_path, destination_path):
# templates at ``source_path`` (relative to your ENV_TEMPLATE_ROOTS) will be
# rendered to ``source_path/destination_path`` (relative to your Tutor environment).
# For example, ``{{ cookiecutter.module_name }}/templates/{{ cookiecutter.plugin_name }}/build``
# will be rendered to ``$(tutor config printroot)/env/plugins/{{ cookiecutter.plugin_name }}/build``.
[
("{{ cookiecutter.plugin_name }}/build", "plugins"),
("{{ cookiecutter.plugin_name }}/apps", "plugins"),
],
)
########################################
# PATCH LOADING
# (It is safe & recommended to leave
# this section as-is :)
########################################
# For each file in {{ cookiecutter.module_name }}/patches,
# apply a patch based on the file's name and contents.
for path in glob(str(importlib_resources.files("{{ cookiecutter.module_name }}") / "patches" / "*")):
with open(path, encoding="utf-8") as patch_file:
hooks.Filters.ENV_PATCHES.add_item((os.path.basename(path), patch_file.read()))
########################################
# CUSTOM JOBS (a.k.a. "do-commands")
########################################
# A job is a set of tasks, each of which run inside a certain container.
# Jobs are invoked using the `do` command, for example: `tutor local do importdemocourse`.
# A few jobs are built in to Tutor, such as `init` and `createuser`.
# You can also add your own custom jobs:
# To add a custom job, define a Click command that returns a list of tasks,
# where each task is a pair in the form ("<service>", "<shell_command>").
# For example:
### @click.command()
### @click.option("-n", "--name", default="plugin developer")
### def say_hi(name: str) -> list[tuple[str, str]]:
### """
### An example job that just prints 'hello' from within both LMS and CMS.
### """
### return [
### ("lms", f"echo 'Hello from LMS, {name}!'"),
### ("cms", f"echo 'Hello from CMS, {name}!'"),
### ]
# Then, add the command function to CLI_DO_COMMANDS:
## hooks.Filters.CLI_DO_COMMANDS.add_item(say_hi)
# Now, you can run your job like this:
# $ tutor local do say-hi --name="{{ cookiecutter.author }}"
#######################################
# CUSTOM CLI COMMANDS
#######################################
# Your plugin can also add custom commands directly to the Tutor CLI.
# These commands are run directly on the user's host computer
# (unlike jobs, which are run in containers).
# To define a command group for your plugin, you would define a Click
# group and then add it to CLI_COMMANDS:
### @click.group()
### def {{ cookiecutter.plugin_name }}() -> None:
### pass
### hooks.Filters.CLI_COMMANDS.add_item({{ cookiecutter.plugin_name }})
# Then, you would add subcommands directly to the Click group, for example:
### @{{ cookiecutter.plugin_name }}.command()
### def example_command() -> None:
### """
### This is helptext for an example command.
### """
### print("You've run an example command.")
# This would allow you to run:
# $ tutor {{ cookiecutter.plugin_name }} example-command