Skip to content

Custom branding [packit-deploy] #12

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

Merged
merged 33 commits into from
May 30, 2025
Merged

Custom branding [packit-deploy] #12

merged 33 commits into from
May 30, 2025

Conversation

david-mears-2
Copy link
Contributor

@david-mears-2 david-mears-2 commented May 1, 2025

This branch deals with all custom branding config. Twinned with mrc-ide/packit#202

How custom branding works

This should be clear from the 'Custom branding config' section of README.md; let me know if not.

Test this branch locally

Follow the steps in the README, using the provided example config basicauthcustombrand to see the app with custom branding applied, and any other (e.g. basicauth) to see that it still works without branding configuration.

When you do packit start <config> make sure to add the --pull option so that you're using the latest version of the packit branch. When using any basicauth-based config, you'll need to use the ./scripts/create-super-user script if you want to see the app in a logged-in state.

Other changes

Constellation version is upgraded so we can make use of bind mounts. Bind mounts are useful here because they let us easily mount files, rather than directories (so we don't end up mounting over the whole public folder...), and it's more complicated to write a file into a volume. (Options to do that include getting a special volume driver that allows pre-populating, or using a temporary container with the file bind-mounted and copying it into the volume there.)

As a result of upgrading the constellation version, the test workflow explicitly pulls images. This is due to a change in constellation 1.2.3 - see vimc/orderly-web-deploy#56 for an analogous fix.

Before merging

Copy link

codecov bot commented May 2, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 95.12%. Comparing base (82904c5) to head (d87bdff).
Report is 34 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #12      +/-   ##
==========================================
+ Coverage   91.93%   95.12%   +3.19%     
==========================================
  Files           4        4              
  Lines         248      308      +60     
  Branches       28       44      +16     
==========================================
+ Hits          228      293      +65     
+ Misses         12        7       -5     
  Partials        8        8              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This is due to a change in constellation 1.2.3 - see vimc/orderly-web-deploy#56
for an analogous fix
I realised firstly that:
- We need to be able to customise branding config in scenarios where packit-deploy
does not provide the proxy, and its provided otherwise, as is the set-up in Montagu.
Montagu handles the proxy in that case.
- But running packit-deploy in a proxy-less mode doesn't in fact make knowing the
app file locations complicated, since the Packit app Dockerfile specifies the location
for the built app to be copied to, so we can rely on that to be the case for both
proxy-less and non-proxy-less modes of packit-deploy.
@david-mears-2 david-mears-2 changed the title WIP custom branding Custom branding May 16, 2025
@david-mears-2 david-mears-2 marked this pull request as ready for review May 16, 2025 14:34
@david-mears-2 david-mears-2 changed the title Custom branding Custom branding [packit-deploy] May 16, 2025
@david-mears-2 david-mears-2 requested a review from richfitz May 19, 2025 11:11
Copy link
Contributor

@EmmaLRussell EmmaLRussell left a comment

Choose a reason for hiding this comment

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

Looks great! 😎

README.md Outdated
@@ -117,3 +121,31 @@ docker exec -it packit-packit-db psql -U packituser -d packit
```

If you have anything else running on port 80 or 443, nothing will work as expected; either stop that service or change the proxy port in the configuration that you are using.

## Custom branding config
Copy link
Contributor

Choose a reason for hiding this comment

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

This section is really useful. Might be worth explicitly stating which yml values each of these features correspond to - or at least pointing to the relevant example config.

README.md Outdated

## Custom branding config

Custom branding is disabled unless both a logo and brand name are configured, since we don't want to display an incorrect combination of brand name and logo/favicon.
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure about this - I feel like the brand name could be the minimum as you might want to just show the default logo if you want to give the app a project-centred name but don't have a bespoke logo.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough. I'll make it so all settings are optional.

@@ -42,10 +42,10 @@ outpack:
packit:
api:
name: packit-api
tag: main
tag: mrc-6035-custom-branding # revert to main after mrc-6035-custom-branding is merged
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm a bit surprised that you needed to update these tags for configurations which don't make use of custom branding..?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I don't remember why, I do remember needing them in one case and thinking what the hell I may as well put it everywhere.

Comment on lines 133 to 137
accent: "hsl(0 100% 50%)"
accent_foreground: "hsl(123 100% 50%)"
dark:
accent: "hsl(30 100% 50%)"
accent_foreground: "hsl(322, 50%, 87%)"
Copy link
Contributor

Choose a reason for hiding this comment

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

For those who don't natively speak hsl, it might be nice to put comments describing each of these colours.


## Branding configuration
## If only one of 'name' or 'logo_path' is set, both will be ignored.
brand:
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess an alternative would have been to put each of these values under the key of the relevant container rather than having the top level "brand" key. I think it makes sense to keep them together, but I wonder if grouping them into "app" and "api" values here might be useful to clearly demarcate which values are applied in which container.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is no such clear demarcation, since the logo file path is used by the configuration of both the app and the api.

Copy link
Contributor

Choose a reason for hiding this comment

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

There is a difference in which containers the deploy tool applies the values to - the app container uses the logo path, but it gets it from the api without any explicit tinkering from the deploy tool, which needs to provide the logo to the api.

But the user of the deployment isn't necessarily going to be thinking about it those terms (though they might be!) so fine as is.

Comment on lines +34 to +40
docker pull redis:5.0
docker pull mrcide/outpack.orderly:main
docker pull mrcide/outpack_server:main
docker pull mrcide/packit-db:main
docker pull mrcide/packit-api:main
docker pull mrcide/packit:main
docker pull mrcide/packit-proxy:main
Copy link
Contributor

Choose a reason for hiding this comment

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

This is for integration testing? You should be able to start the Packit constellation with the --pull flag. You might be pulling the wrong branches if you rely on this.

Copy link
Member

Choose a reason for hiding this comment

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

this is really a constellation issue that I need to fix at some point

@@ -75,6 +77,39 @@ def __init__(self, path, extra=None, options=None):
else:
self.proxy_enabled = False

self.branding_enabled = bool(dat.get("brand", {}).get("name") and dat.get("brand", {}).get("logo_path"))
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels a little easier to reason about, though I assume the logic shakes down correctly with Python casting.

Suggested change
self.branding_enabled = bool(dat.get("brand", {}).get("name") and dat.get("brand", {}).get("logo_path"))
self.branding_enabled = bool(dat.get("brand", {}).get("name")) and bool(dat.get("brand", {}).get("logo_path"))

Also, it might be tidier to pull out the brand config once, before this line and then query that rather than query from the top level a bunch of times e.g. brand_config = dat.get("brand, {})

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've removed the whole concept of 'branding enabled/disabled'.

Comment on lines 125 to 128
try:
env.update({"PACKIT_BRAND_LOGO_LINK": cfg.brand_logo_link})
except AttributeError:
print("[packit-api] A brand logo link was not provided (optional)")
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just check if the attribute is set and not attempt to set the value if it isn't provided?

Comment on lines 123 to 124
env.update({"PACKIT_BRAND_LOGO_NAME": cfg.brand_logo_name})
env.update({"PACKIT_BRAND_LOGO_ALT_TEXT": cfg.brand_logo_alt_text})
Copy link
Contributor

Choose a reason for hiding this comment

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

env is just an ordinary dictionary so you could just set values like
env["PACKIT_BRAND_LOGO_NAME"] = cfg.brand_logo_name.

Or conversely you could take advantage of update and set both values at once:

Suggested change
env.update({"PACKIT_BRAND_LOGO_NAME": cfg.brand_logo_name})
env.update({"PACKIT_BRAND_LOGO_ALT_TEXT": cfg.brand_logo_alt_text})
env.update({
"PACKIT_BRAND_LOGO_NAME": cfg.brand_logo_name,
"PACKIT_BRAND_LOGO_ALT_TEXT": cfg.brand_logo_alt_text})

Copy link
Member

@richfitz richfitz left a comment

Choose a reason for hiding this comment

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

This is looking good when run up; comments, questions and suggestions inline

README.md Outdated

### Logo (required)

The logo file is bind-mounted into the front-end container, in a public folder, and the packit api has an env var set for the filename of the logo, so that it can tell the front end where to look for the file. Your logo file should be in the same directory as the config file.
Copy link
Member

Choose a reason for hiding this comment

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

What size should this be as a suggestion? Does it matter or is is just resized on the fly? Why is it required? Can we not fall back on the default version if it is not found?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What size should this be as a suggestion? Does it matter or is is just resized on the fly?

I'll put in a comment to say that the logo will be constrained to the height of the header and the width is kept proportional.

Why is it required?

Removed requirement.

Can we not fall back on the default version if it is not found?

Let's discuss over at #12 (comment)

README.md Outdated

The logo file is bind-mounted into the front-end container, in a public folder, and the packit api has an env var set for the filename of the logo, so that it can tell the front end where to look for the file. Your logo file should be in the same directory as the config file.

### Logo alt text (optional)
Copy link
Member

Choose a reason for hiding this comment

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

Should this be alt text or title text? Should it be possible to set both?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, those are separately configurable. Title text is referred to as 'brand name'.


### Logo link (optional)

This is to allow a configurable link destination for when the user clicks the logo. In VIMC's case this would be a link back to Montagu. This is set as an env var in the packit api, which passes it on to the front end.
Copy link
Member

Choose a reason for hiding this comment

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

Does the fact that these come through as an envvar matter for the README?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dunno, it might be useful documentation, but the problem with documentation is that it gets out of sync with reality. @EmmaLRussell you are someone who has found this readme useful, what do you reckon?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think a bit of docs on the mechanics of how these values work is useful once it's fairly stable, as it's quite bitty and easy to forget which values go where ..


### Brand colors (optional)

The brand colors are written as css variables into the public custom.css file, which override default variables in the front-end. If no colors are provided for the dark theme, the light theme colors are reused.
Copy link
Member

Choose a reason for hiding this comment

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

List colours that can be modified here?

# We configure the title tag of the index.html file here, rather than updating it dynamically with JS,
# since using JS results in the page title visibly changing a number of seconds after the initial page load.
substitute_file_content(
container, f"{cfg.app_html_root}/index.html", r"(?<=<title>).*?(?=</title>)", cfg.brand_name
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

f" --custom-accent-foreground: {cfg.brand_accent_foreground_dark};\n"
"}\n"
)
substitute_file_content(container, f"{cfg.app_html_root}/css/custom.css", r".*", new_css, flags=re.DOTALL)
Copy link
Member

Choose a reason for hiding this comment

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

is this just (over)writing a file? Can we do this more obviously?

Copy link
Contributor Author

@david-mears-2 david-mears-2 May 22, 2025

Choose a reason for hiding this comment

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

It is. I can make the fact it's doing this more obvious by wrapping substitute_file_content with a method overwrite_file. It's somewhat convenient (for developers of packit) to have a file custom.css in the packit repo, which explains in comments where the custom css variables will come from in production, and which they can use to play with setting custom variables.

substitute_file_content(
container, f"{cfg.app_html_root}/index.html", r"(?<=<title>).*?(?=</title>)", cfg.brand_name
)
substitute_file_content(container, f"{cfg.app_html_root}/index.html", r"favicon\.ico", cfg.brand_favicon_name)
Copy link
Member

Choose a reason for hiding this comment

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

I'm confused on this, and perhaps generally, why the index.html needs writing at all. Can we not have a path to the content that never changes (say /favicon.ico) and then have the backend try first (say) custom/favicon.ico then fall back on baked-in/favicon.ico for example? With the same strategy for other resources.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I want to have configurable favicon filename so that people can use any type of file, which might require a different extension than .ico.

Copy link
Contributor Author

@david-mears-2 david-mears-2 May 22, 2025

Choose a reason for hiding this comment

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

With regards to other resources - of which there is one, the logo - the same argument could apply. It's also useful to have the logo file be named whatever, so that you can maintain an obvious relationship between it and the file in your organisation's perhaps-chaotic dropbox folder of various different logo variants and versions.

Comment on lines +59 to +60
# Secret used to generate JWT tokens - this can be any string, the secret at this key in the vault is a random
# 32 char string, and is probably fine to re-use
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# Secret used to generate JWT tokens - this can be any string, the secret at this key in the vault is a random
# 32 char string, and is probably fine to re-use
# Secret used to generate JWT tokens - this can be any string

@richfitz richfitz merged commit 5cfc2e4 into main May 30, 2025
8 checks passed
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.

3 participants