Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redockerize and deproxify #58

Draft
wants to merge 22 commits into
base: develop
Choose a base branch
from
Draft

Conversation

jgonggrijp
Copy link
Contributor

@jgonggrijp jgonggrijp commented Nov 5, 2024

This is a work in progress. This description will likely be updated at least a few times in the future. I will certainly rebase and force-push the branch multiple times.

I am trying to close #16 and close #35 at the same time. My intention is to first get everything working in Docker and then git rid of the proxy situation. I'm currently having some trouble with Docker and am hoping that @BeritJanssen can take a look. See my own review below for details.

Pre-existing situation Intended result
Generator needs to have Python 3.10 (?), cookiecutter, node.js 18 and @angular/cli installed Generator needs to have some version of Python 3, cookiecutter and Docker installed
Developer needs to have Python 3.10, node.js 18, PostgreSQL>10 and all other dependencies installed Developer only needs Docker on the host machine
Frontend and backend proxy to each other via complex injected configuration HTTP server (probably nginx) reverse proxies to both, no configuration injection needed

I envision the global workflow as follows:

  1. cookiecutter generates initial files based on template directory. This part doesn't change much.
  2. Generation of additional files such as in ng new is taken care of in the post-generation hook using docker-compose. To this end, the frontend and backend both have a special Dockerfile-postgenerate and the project root has a special compose-postgenerate.yml, which get deleted right after generation is done. This is the part where I'm currently having trouble.
  3. End result of the cookiecutter is a directory that is ready to be committed to git and shared with other developers. This part doesn't fundamentally change.
  4. The code tree is ready to run with docker-compose up. It is no longer necessary to run python bootstrap.py on the first run after cloning. Some of the logic in the bootstrap.py is still needed during generation, but it can be merged into the hooks/post_gen_project.py.

Copy link
Contributor Author

@jgonggrijp jgonggrijp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BeritJanssen here is a description of what I'm currently running into. More details in the comments below.

The generation of the Angular frontend proceeds roughly as follows. Notice that it is a back-and-forth between Python on the host machine (odd steps) and node.js inside Docker containers (even steps).

  1. The frontend.angular directory comes rendered out of the cookiecutter main template run. All steps after this happen during the post-generation hook.
  2. A new Angular project directory with the name substituted for {{cookiecutter.npm_slug}} is created using ng new. The contents of frontend.angular are copied into this directory after it is created. This happens in the bootstrap stage in the Dockerfile-postgenerate. A host directory named just frontend is also bind mounted into the container.
  3. The properties in the frontend.angular/package.overwrite.json are merged into the {{cookiecutter.npm_slug}}/package.json. This is what happens in between Dockerfile stages with CMD cp, the first call to override_json in the bootstrap.py:activate_frontend and finally COPY.
  4. ng add @angular/localize is run inside the {{cookiecutter.npm_slug}} directory. This happens in the bootstrap-2 stage.
  5. The angular.json is customized with a procedure like step 3.
  6. ng extract-i18n is run inside the {{cookiecutter.npm_slug}} directory in the bootstrap-3 stage. After this, all contents of the {{cookiecutter.npm_slug}} directory (which only exists in the container) are copied to the frontend bind mount.
  7. Further modifications to the locale files happen inside the frontend directory in Python on the host machine.

Steps 4-7 are currently commented out, so you can run step 4 manually and reproduce the issue that I run into. Steps to reproduce:

  1. Clone the cookiecutter-webapp-deluxe repo (if necessary) and checkout the feature/redockerize-deproxify branch.
  2. Use your local clone to generate a scrap project using cookiecutter ../relative/path/to/cookiecutter-webapp-deluxe. Tip: you can run this again with the exact same settings, without having to answer all the questions again, by adding the --replay flag.
  3. cd into the generated project.
  4. Run docker-compose -f compose-postgenerate.yml --profile bootstrap-2 run frontend-2 sh. This is the manual version of step 4 in the previous list.
  5. Observe the contents of the /usr/src/app/{{cookiecutter.npm_slug}} directory inside the container. Notice that the COPY command on line 16 of the Dockerfile-postgenerate appears to have been undone. In other words, the project directory appears to be pristine as it was right after ng new, with all subsequent customizations somehow having disappeared.

I hope you understand what is going on here!

Comment on lines -44 to -102
venv = create_virtualenv()
pip_tools = backreq = backpack = clone_req = funcreq = funcpack = False
if venv:
adopt_virtualenv(VIRTUALENV)
pip_tools = install_pip_tools()
if pip_tools:
check_version_pip()
check_version_pip_compile()
backreq = compile_backend_requirements()
if backreq:
backpack = install_backend_packages()
clone_req = copy_backreq_to_func()
if clone_req:
funcreq = compile_functest_requirements()
if funcreq:
funcpack = install_functest_packages()
frontpack = install_frontend_packages()
git = setup_git()
gitflow = develop = stage = commit = remote = False
if git:
gitflow = setup_gitflow()
if not gitflow:
develop = create_develop_branch()
if funcreq and frontpack:
stage = git_add()
if stage:
commit = initial_commit()
remote = add_remote()
db = create_db()
if db:
db = grant_db()
migrate = superuser = False
if db and backpack:
migrate = run_migrations()
if migrate:
superuser = create_superuser()
if not all([superuser, remote, commit, gitflow, funcpack]):
print('\nPlease read {} for information on failed commands.'.format(LOGFILE_NAME))
print('\nAlmost ready to go! Just a couple more commands to run:')
print(cd_into_project)
if not venv: print(create_virtualenv)
print(activate_venv)
if not pip_tools: print(install_pip_tools)
if not backreq: print(compile_backend_requirements)
if not clone_req: print(copy_backreq_to_func)
if not funcreq: print(compile_functest_requirements)
if not (backpack and frontpack and funcpack): print(install_all_packages)
if not git: print(setup_git)
if not gitflow and not develop: print(create_develop_branch)
if not stage: print(git_add)
if not commit: print(initial_commit)
if not remote: print(add_remote)
if not db:
print(create_db)
print(grant_db)
if not migrate: print(run_migrations)
if not superuser: print(create_superuser)
print(git_push)
print(yarn_start)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some, but not all, of this logic is or will be restored via the bootstrap_subprojects command, which relies on docker-compose.

@@ -121,6 +66,8 @@ def generate_backbone_translations():
return True


bootstrap_subprojects = make_bootstrap_command('bootstrap')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make_bootstrap_command is currently defined in the bootstrap.py, but will move here.

Comment on lines 215 to 232
project_name = '{{cookiecutter.slug}}'.replace('_', '-')
Command(
'Install dependencies',
['yarn', 'install', '--ignore-scripts']
)()
Command(
'Creating project',
['yarn', 'ng', 'new', project_name, '--prefix={{cookiecutter.app_prefix}}',
'--ssr',
'--skip-git=true',
'--skip-install=true',
'--package-manager=yarn',
'--style=scss',
'--routing=true']
)()
shutil.copytree('frontend.angular', project_name, dirs_exist_ok=True)
os.rename(project_name, 'frontend')
shutil.move(op.join('frontend', 'proxy.conf.json'), 'proxy.conf.json')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the old activation logic is implemented in the bootstrap stage of the frontend.angular/Dockerfile-postgenerate. This already works as intended.

)()
shutil.copytree('frontend.angular', project_name, dirs_exist_ok=True)
os.rename(project_name, 'frontend')
shutil.move(op.join('frontend', 'proxy.conf.json'), 'proxy.conf.json')
override_json('package')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is simpy retained and (still) works as intended. The implementation will be moved to the hooks/post_gen_project.py, though.

Comment on lines -234 to -247
Command(
'Install frontend dependencies using Yarn',
['yarn'],
cwd="frontend"
)()
# Remove favicon.ico
os.remove(os.path.join('frontend', 'src', 'favicon.ico'))
# Remove editorconfig
os.remove(os.path.join('frontend', '.editorconfig'))
Command(
'ng add @angular/localize',
['yarn', 'ng', 'add', '@angular/localize', '--skip-confirmation'],
cwd="frontend"
)()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is implemented in the bootstrap-2 stage of the frontend.angular/Dockerfile-postgenerate. It doesn't work as intended for a surprising reason, which I'll highlight in the Dockerfile.

['yarn', 'ng', 'config', "projects.{{cookiecutter.slug | replace('_', '-')}}.architect.serve.options.port", '{{cookiecutter.frontend_port}}'],
cwd="frontend"
)()
# if not angular_bootstrap_2():
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the part where the bootstrap-2 stage of the Dockerfile-postgenerate gets to do its tricks.

# if not angular_bootstrap_2():
# return false
# override_json('angular')
# if not angular_bootstrap_3():
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise for the bootstrap-3 stage.

Comment on lines 260 to 271
def make_bootstrap_command(profile):
return Command(
f'Finalize subproject package configuration ({profile})',
['docker', 'compose',
'-f', 'compose-postgenerate.yml',
'--profile', profile,
'up']
)


angular_bootstrap_2 = make_bootstrap_command('bootstrap-2')
angular_bootstrap_3 = make_bootstrap_command('bootstrap-3')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like activate_frontend, this logic will move to the hooks/post_gen_project.py.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this compose file has three profiles:

  • bootstrap, which runs as part of hooks/post_gen_project.py:main.
  • bootstrap-2, which runs as part of bootstrap.py:activate_frontend.
  • bootstrap-3, which also runs as part of bootstrap.py:activate_frontend.

The names of the profiles are the same as, and correspond to, the stages in the frontend.angular/Dockerfile-postgenerate. bootstrap-2 and bootstrap-3 are only needed for Angular.

yarn ng add @angular/localize --skip-confirmation

VOLUME /usr/src/app/frontend
CMD cp angular.json angular.overwrite.json ../frontend/
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the surprising part that I'm currently running into. At this point in the image layering, the contents of /usr/src/app/{{cookiecutter.npm_sug}} appear to have been reset to how they were at the WORKDIR on line 15. @BeritJanssen do you understand what is going on here?

@jgonggrijp
Copy link
Contributor Author

@BeritJanssen and I discussed the above issue on Teams. Some notes, mostly as a reminder to self:

  • Files are not automatically copied between Dockerfile stages, I need to do COPY --from=other-stage-name.
  • Stages need not derive from previous stages, they can have different base images. So I can interlace node and Python-based operations within the same Dockerfile.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unsupported combination of django-proxy and Django Dockerize
1 participant