mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2025-12-26 03:43:34 -05:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e78323d214 | ||
|
|
d2e866dd74 | ||
|
|
76687ad5df | ||
|
|
dab77e8e4f | ||
|
|
0b250c71aa | ||
|
|
571f670db0 | ||
|
|
4e9e628162 | ||
|
|
4f49b06704 | ||
|
|
8eb0c36665 | ||
|
|
6f69c09aca | ||
|
|
8e6f153882 | ||
|
|
07183fd40f | ||
|
|
04b7f0a398 | ||
|
|
1735fda48f | ||
|
|
1c9ea0eda7 | ||
|
|
83b5b6695c | ||
|
|
342fb3c96d | ||
|
|
b7a18466b5 | ||
|
|
0cdc4d51df | ||
|
|
e177669514 | ||
|
|
fd294dfcdd | ||
|
|
bdd092e5d3 | ||
|
|
f1c5a0ef5f | ||
|
|
1e8ff763d5 | ||
|
|
4cf6a3b219 | ||
|
|
de145b6b18 | ||
|
|
84a8308bf3 | ||
|
|
8d191fa1a1 | ||
|
|
b47a0197e2 | ||
|
|
4e7c5f9495 | ||
|
|
d704ddacdd | ||
|
|
2c3140248c |
@@ -1,18 +1,18 @@
|
||||
name: Deploy Docker Image
|
||||
|
||||
name: publish dev image docker
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
branches:
|
||||
- '*'
|
||||
- '*/*'
|
||||
- '!master'
|
||||
jobs:
|
||||
build-push:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Publish to Registry
|
||||
uses: AhnSeongHyun/action-tag-docker-build-push@v1.0.0
|
||||
uses: elgohr/Publish-Docker-Github-Action@2.13
|
||||
with:
|
||||
repo_name: vabene1111/recipes
|
||||
name: vabene1111/recipes
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
19
.github/workflows/docker-publish-latest.yml
vendored
Normal file
19
.github/workflows/docker-publish-latest.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
name: publish latest image docker
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Build and publish image
|
||||
uses: ilteoood/docker_buildx@master
|
||||
with:
|
||||
publish: true
|
||||
imageName: vabene1111/recipes
|
||||
tag: latest
|
||||
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
|
||||
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}
|
||||
25
.github/workflows/docker-publish-release.yml
vendored
Normal file
25
.github/workflows/docker-publish-release.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: publish tagged release docker
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build image job
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@master#
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
|
||||
- name: Build and publish image
|
||||
uses: ilteoood/docker_buildx@master
|
||||
with:
|
||||
publish: true
|
||||
imageName: vabene1111/recipes
|
||||
tag: ${{ steps.get_version.outputs.VERSION }}
|
||||
dockerHubUser: ${{ secrets.DOCKER_USERNAME }}
|
||||
dockerHubPassword: ${{ secrets.DOCKER_PASSWORD }}
|
||||
13
.github/workflows/docker-publish.yml
vendored
13
.github/workflows/docker-publish.yml
vendored
@@ -1,13 +0,0 @@
|
||||
name: Publish Docker
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Publish to Registry
|
||||
uses: elgohr/Publish-Docker-Github-Action@2.13
|
||||
with:
|
||||
name: vabene1111/recipes
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
@@ -25,7 +25,7 @@ The docker image (`vabene1111/recipes`) simply exposes the application on port `
|
||||
|
||||
### Docker-Compose
|
||||
|
||||
2. Choose one of the included configurations [here](https://github.com/vabene1111/recipes/tree/develop/docs/docker).
|
||||
2. Choose one of the included configurations [here](docs/docker).
|
||||
2. Download the environment (config) file template and fill it out `wget https://raw.githubusercontent.com/vabene1111/recipes/develop/.env.template -O .env `
|
||||
3. Start the container `docker-compose up -d`
|
||||
4. Create a default user by running `docker-compose exec web_recipes createsuperuser`.
|
||||
@@ -46,6 +46,10 @@ While intermediate updates can be skipped when updating please make sure to **re
|
||||
2. Pull the latest image using `docker-compose pull`
|
||||
3. Start the container again using `docker-compose up -d`
|
||||
|
||||
## Kubernetes
|
||||
|
||||
You can find a basic kubernetes setup [here](docs/k8s/). Please see the README in the folder for more detail.
|
||||
|
||||
# Documentation
|
||||
|
||||
Most things should be straight forward but there are some more complicated things.
|
||||
|
||||
@@ -31,10 +31,11 @@ class UserPreferenceForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = UserPreference
|
||||
fields = ('theme', 'nav_color')
|
||||
fields = ('default_unit', 'theme', 'nav_color', 'default_page')
|
||||
|
||||
help_texts = {
|
||||
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!')
|
||||
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'),
|
||||
'default_unit': _('Default Unit to be used when inserting a new ingredient into a recipe.')
|
||||
}
|
||||
|
||||
|
||||
|
||||
18
cookbook/migrations/0031_auto_20200407_1841.py
Normal file
18
cookbook/migrations/0031_auto_20200407_1841.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.4 on 2020-04-07 16:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0030_recipeingredient_note'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='keyword',
|
||||
name='icon',
|
||||
field=models.CharField(blank=True, max_length=16, null=True),
|
||||
),
|
||||
]
|
||||
18
cookbook/migrations/0032_userpreference_default_unit.py
Normal file
18
cookbook/migrations/0032_userpreference_default_unit.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.4 on 2020-04-13 20:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0031_auto_20200407_1841'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userpreference',
|
||||
name='default_unit',
|
||||
field=models.CharField(default='g', max_length=32),
|
||||
),
|
||||
]
|
||||
18
cookbook/migrations/0033_userpreference_default_page.py
Normal file
18
cookbook/migrations/0033_userpreference_default_page.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 3.0.4 on 2020-04-13 20:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0032_userpreference_default_unit'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='userpreference',
|
||||
name='default_page',
|
||||
field=models.CharField(choices=[('SEARCH', 'Search'), ('PLAN', 'Meal-Plan')], default='SEARCH', max_length=64),
|
||||
),
|
||||
]
|
||||
@@ -41,9 +41,18 @@ class UserPreference(models.Model):
|
||||
|
||||
COLORS = ((PRIMARY, 'Primary'), (SECONDARY, 'Secondary'), (SUCCESS, 'Success'), (INFO, 'Info'), (WARNING, 'Warning'), (DANGER, 'Danger'), (LIGHT, 'Light'), (DARK, 'Dark'))
|
||||
|
||||
# Default Page
|
||||
SEARCH = 'SEARCH'
|
||||
PLAN = 'PLAN'
|
||||
BOOKS = 'BOOKS'
|
||||
|
||||
PAGES = ((SEARCH, _('Search')), (PLAN, _('Meal-Plan')), (BOOKS, _('Books')), )
|
||||
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
|
||||
theme = models.CharField(choices=THEMES, max_length=128, default=FLATLY)
|
||||
nav_color = models.CharField(choices=COLORS, max_length=128, default=PRIMARY)
|
||||
default_unit = models.CharField(max_length=32, default='g')
|
||||
default_page = models.CharField(choices=PAGES, max_length=64, default=SEARCH)
|
||||
|
||||
def __str__(self):
|
||||
return self.user
|
||||
@@ -90,7 +99,7 @@ class SyncLog(models.Model):
|
||||
|
||||
class Keyword(models.Model):
|
||||
name = models.CharField(max_length=64, unique=True)
|
||||
icon = models.CharField(max_length=1, blank=True, null=True)
|
||||
icon = models.CharField(max_length=16, blank=True, null=True)
|
||||
description = models.TextField(default="", blank=True)
|
||||
created_by = models.IntegerField(default=0)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@@ -16,9 +16,10 @@ class Nextcloud(Provider):
|
||||
@staticmethod
|
||||
def get_client(storage):
|
||||
options = {
|
||||
'webdav_hostname': storage.url + '/remote.php/dav/files/' + storage.username,
|
||||
'webdav_hostname': storage.url,
|
||||
'webdav_login': storage.username,
|
||||
'webdav_password': storage.password
|
||||
'webdav_password': storage.password,
|
||||
'webdav_root': '/remote.php/dav/files/' + storage.username
|
||||
}
|
||||
return wc.Client(options)
|
||||
|
||||
|
||||
23
cookbook/static/tabulator/tabulator.min.js
vendored
23
cookbook/static/tabulator/tabulator.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -74,8 +74,8 @@
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item {% if request.resolver_match.url_name in 'index,edit_recipe,edit_internal_recipe,edit_external_recipe,view_recipe' %}active{% endif %}">
|
||||
<a class="nav-link" href="{% url 'index' %}"><i class="fas fa-book"></i> {% trans 'Cookbook' %}<span
|
||||
<li class="nav-item {% if request.resolver_match.url_name in 'view_search,edit_recipe,edit_internal_recipe,edit_external_recipe,view_recipe' %}active{% endif %}">
|
||||
<a class="nav-link" href="{% url 'view_search' %}"><i class="fas fa-book"></i> {% trans 'Cookbook' %}<span
|
||||
class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
</div>
|
||||
{% if field.name == 'name' %}
|
||||
<label>{% trans 'Ingredients' %}</label>
|
||||
{{ form.ingredients.errors }}
|
||||
<div id="ingredients-table"></div>
|
||||
<br>
|
||||
<div class="table-controls" style="text-align: center">
|
||||
@@ -144,7 +145,7 @@
|
||||
validator: "required",
|
||||
editor: select2IngredientEditor
|
||||
},
|
||||
{title: "{% trans 'Amount' %}", field: "amount", validator: "required", editor: "input"},
|
||||
{title: "{% trans 'Amount' %}", field: "amount", validator: "required", editor: "number"},
|
||||
{
|
||||
title: "{% trans 'Unit' %}",
|
||||
field: "unit__name",
|
||||
@@ -188,7 +189,7 @@
|
||||
data.push({
|
||||
ingredient__name: "{% trans 'Ingredient' %}",
|
||||
amount: "100",
|
||||
unit__name: "g",
|
||||
unit__name: "{{ request.user.userpreference.default_unit }}",
|
||||
note: "",
|
||||
id: Math.floor(Math.random() * 10000000),
|
||||
delete: false,
|
||||
|
||||
@@ -8,6 +8,25 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.mealplan-cell .mealplan-add-button{
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.mealplan-cell .mealplan-add-button{
|
||||
visibility: hidden;
|
||||
float: right;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.mealplan-cell:hover .mealplan-add-button{
|
||||
visibility: initial;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<h3>
|
||||
{% trans 'Meal-Plan' %} <a href="{% url 'new_meal_plan' %}"><i class="fas fa-plus-circle"></i></a>
|
||||
@@ -53,7 +72,8 @@
|
||||
</tr>
|
||||
<tr>
|
||||
{% for day_key, days_value in plan_value.days.items %}
|
||||
<td>
|
||||
<td class="mealplan-cell">
|
||||
<a class="mealplan-add-button" href="{% url 'new_meal_plan' %}?date={{ day_key|date:'Y-m-d' }}&meal={{ plan_key }}"><i class="fas fa-plus"></i></a>
|
||||
{% for mp in days_value %}
|
||||
<a href="{% url 'edit_meal_plan' mp.pk %}"><i class="fas fa-edit"></i></a>
|
||||
<a href="{% url 'view_recipe' mp.recipe.id %}">{{ mp.recipe.name }}</a><br/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% load l10n %}
|
||||
{% load custom_tags %}
|
||||
|
||||
{% block title %}{% trans 'View' %}{% endblock %}
|
||||
{% block title %}{{ recipe.name }}{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pretty-checkbox@3.0/dist/pretty-checkbox.min.css"
|
||||
@@ -60,6 +60,8 @@
|
||||
{% endif %}
|
||||
<a class="btn btn-info" href="{% url 'new_meal_plan' %}?recipe={{ recipe.pk }}"><i
|
||||
class="fas fa-calendar"></i></a>
|
||||
<a class="btn btn-light" onclick="window.print()"><i
|
||||
class="fas fa-print"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -98,7 +100,7 @@
|
||||
|
||||
<div class="row">
|
||||
{% if ingredients %}
|
||||
<div class="col-lg-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
|
||||
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
@@ -148,12 +150,16 @@
|
||||
</td>
|
||||
<td style="vertical-align: middle!important;">
|
||||
{% if i.note %}
|
||||
<button class="btn btn-light btn-sm" type="button" data-container="body"
|
||||
<button class="btn btn-light btn-sm d-print-none" type="button"
|
||||
data-container="body"
|
||||
data-toggle="popover"
|
||||
data-placement="right" data-html="true" data-trigger="focus"
|
||||
data-content="{{ i.note }}">
|
||||
<i class="fas fa-info"></i>
|
||||
</button>
|
||||
<div class="d-none d-print-block">
|
||||
<i class="far fa-comment-alt"></i> {{ i.note }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -173,7 +179,7 @@
|
||||
|
||||
{% endif %}
|
||||
{% if recipe.image %}
|
||||
<div class="col-md-6 order-md-2 col-sm-12 order-sm-1 col-12 order-1 " style="text-align: center">
|
||||
<div class="col-12 order-1 col-sm-12 order-sm-1 col-md-6 order-md-2" style="text-align: center">
|
||||
<img class="img img-fluid rounded" src="{{ recipe.image.url }}" style="max-height: 30vh;"
|
||||
alt="{% trans 'Recipe Image' %}">
|
||||
<br/>
|
||||
@@ -188,7 +194,7 @@
|
||||
|
||||
<div style="font-size: large">
|
||||
{% if recipe.instructions %}
|
||||
{{ recipe.instructions | markdown | safe }}
|
||||
{{ recipe.instructions | markdown | safe | urlize }}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -294,7 +300,8 @@
|
||||
<br/>
|
||||
<br/>
|
||||
|
||||
<h5><i class="far fa-comments"></i> {% trans 'Comments' %}</h5>
|
||||
<h5 {% if not comments %}class="d-print-none" {% endif %}><i class="far fa-comments"></i> {% trans 'Comments' %}
|
||||
</h5>
|
||||
{% for c in comments %}
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
@@ -29,4 +29,4 @@ def delete_url(model, pk):
|
||||
def markdown(value):
|
||||
tags = markdown_tags + ['pre', 'table', 'td', 'tr', 'th', 'tbody', 'style', 'thead']
|
||||
parsed_md = md.markdown(value, extensions=['markdown.extensions.fenced_code', 'tables', MarkdownFormatExtension()])
|
||||
return bleach.clean(parsed_md, tags, print_attrs, markdown_attrs)
|
||||
return bleach.clean(parsed_md, tags, markdown_attrs)
|
||||
|
||||
@@ -7,10 +7,10 @@ class TestViewsGeneral(TestViews):
|
||||
|
||||
def test_index(self):
|
||||
r = self.client.get(reverse('index'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
r = self.anonymous_client.get(reverse('index'))
|
||||
self.assertEqual(r.status_code, 200)
|
||||
self.assertEqual(r.status_code, 302)
|
||||
|
||||
def test_books(self):
|
||||
url = reverse('view_books')
|
||||
|
||||
@@ -8,6 +8,7 @@ from cookbook.helper import dal
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
path('search/', views.search, name='view_search'),
|
||||
path('books/', views.books, name='view_books'),
|
||||
path('plan/', views.meal_plan, name='view_plan'),
|
||||
path('shopping/', views.shopping_list, name='view_shopping'),
|
||||
|
||||
@@ -98,7 +98,10 @@ def internal_recipe_update(request, pk):
|
||||
recipe_ingredient.ingredient = ingredient
|
||||
|
||||
if isinstance(i['amount'], str):
|
||||
recipe_ingredient.amount = float(i['amount'].replace(',', '.'))
|
||||
try:
|
||||
recipe_ingredient.amount = float(i['amount'].replace(',', '.'))
|
||||
except ValueError:
|
||||
form.add_error("ingredients", _('There was an error converting your ingredients amount to a number: ') + i['unit__name'])
|
||||
else:
|
||||
recipe_ingredient.amount = i['amount']
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@@ -120,6 +121,12 @@ class MealPlanCreate(LoginRequiredMixin, CreateView):
|
||||
form_class = MealPlanForm
|
||||
success_url = reverse_lazy('view_plan')
|
||||
|
||||
def get_initial(self):
|
||||
return dict(
|
||||
meal=self.request.GET['meal'] if 'meal' in self.request.GET else None,
|
||||
date=datetime.strptime(self.request.GET['date'], '%Y-%m-%d') if 'date' in self.request.GET else None
|
||||
)
|
||||
|
||||
def form_valid(self, form):
|
||||
obj = form.save(commit=False)
|
||||
obj.user = self.request.user
|
||||
|
||||
@@ -6,7 +6,9 @@ from django.contrib import messages
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.urls import reverse_lazy
|
||||
from django_tables2 import RequestConfig
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
@@ -16,6 +18,21 @@ from cookbook.tables import RecipeTable
|
||||
|
||||
|
||||
def index(request):
|
||||
if not request.user.is_authenticated:
|
||||
return HttpResponseRedirect(reverse_lazy('view_search'))
|
||||
try:
|
||||
page_map = {
|
||||
UserPreference.SEARCH: reverse_lazy('view_search'),
|
||||
UserPreference.PLAN: reverse_lazy('view_plan'),
|
||||
UserPreference.BOOKS: reverse_lazy('view_books'),
|
||||
}
|
||||
|
||||
return HttpResponseRedirect(page_map.get(request.user.userpreference.default_page))
|
||||
except UserPreference.DoesNotExist:
|
||||
return HttpResponseRedirect(reverse_lazy('view_search'))
|
||||
|
||||
|
||||
def search(request):
|
||||
if request.user.is_authenticated:
|
||||
f = RecipeFilter(request.GET, queryset=Recipe.objects.all().order_by('name'))
|
||||
|
||||
@@ -137,7 +154,8 @@ def shopping_list(request):
|
||||
if Recipe.objects.filter(pk=int(r)).exists():
|
||||
recipes.append(int(r))
|
||||
|
||||
form = ShoppingForm(initial={'recipe': recipes})
|
||||
markdown_format = False
|
||||
form = ShoppingForm(initial={'recipe': recipes, 'markdown_format': False})
|
||||
|
||||
ingredients = []
|
||||
|
||||
@@ -174,6 +192,8 @@ def settings(request):
|
||||
up = UserPreference(user=request.user)
|
||||
up.theme = form.cleaned_data['theme']
|
||||
up.nav_color = form.cleaned_data['nav_color']
|
||||
up.default_unit = form.cleaned_data['default_unit']
|
||||
up.default_page = form.cleaned_data['default_page']
|
||||
up.save()
|
||||
|
||||
if 'user_name_form' in request.POST:
|
||||
|
||||
33
docs/k8s/10-configmap.yaml
Normal file
33
docs/k8s/10-configmap.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
kind: ConfigMap
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
labels:
|
||||
app: recipes
|
||||
name: recipes-nginx-config
|
||||
data:
|
||||
nginx-config: |-
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
http {
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
client_max_body_size 16M;
|
||||
|
||||
# serve static files
|
||||
location /static/ {
|
||||
alias /static/;
|
||||
}
|
||||
# serve media files
|
||||
location /media/ {
|
||||
alias /media/;
|
||||
}
|
||||
# pass requests for dynamic content to gunicorn
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_pass http://localhost:8080;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
docs/k8s/30-pv.yaml
Normal file
50
docs/k8s/30-pv.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: recipes-db
|
||||
labels:
|
||||
app: recipes
|
||||
type: local
|
||||
tier: db
|
||||
spec:
|
||||
storageClassName: manual
|
||||
capacity:
|
||||
storage: 1Gi
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
hostPath:
|
||||
path: "/data/recipes/db"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: recipes-media
|
||||
labels:
|
||||
app: recipes
|
||||
type: local
|
||||
tier: media
|
||||
spec:
|
||||
storageClassName: manual
|
||||
capacity:
|
||||
storage: 1Gi
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
hostPath:
|
||||
path: "/data/recipes/media"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: recipes-static
|
||||
labels:
|
||||
app: recipes
|
||||
type: local
|
||||
tier: static
|
||||
spec:
|
||||
storageClassName: manual
|
||||
capacity:
|
||||
storage: 1Gi
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
hostPath:
|
||||
path: "/data/recipes/static"
|
||||
52
docs/k8s/30-pvc.yaml
Normal file
52
docs/k8s/30-pvc.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: recipes-db
|
||||
labels:
|
||||
app: recipes
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
tier: db
|
||||
storageClassName: manual
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: recipes-media
|
||||
labels:
|
||||
app: recipes
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
tier: media
|
||||
app: recipes
|
||||
storageClassName: manual
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: recipes-static
|
||||
labels:
|
||||
app: recipes
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
tier: static
|
||||
app: recipes
|
||||
storageClassName: manual
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
102
docs/k8s/50-deployment.yaml
Normal file
102
docs/k8s/50-deployment.yaml
Normal file
@@ -0,0 +1,102 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: recipes
|
||||
labels:
|
||||
app: recipes
|
||||
environment: production
|
||||
tier: frontend
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: RollingUpdate
|
||||
selector:
|
||||
matchLabels:
|
||||
app: recipes
|
||||
environment: production
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: recipes
|
||||
environment: production
|
||||
spec:
|
||||
containers:
|
||||
- name: recipes-nginx
|
||||
image: nginx:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
volumeMounts:
|
||||
- mountPath: '/media'
|
||||
name: media
|
||||
- mountPath: '/static'
|
||||
name: static
|
||||
- name: nginx-config
|
||||
mountPath: /etc/nginx/nginx.conf
|
||||
subPath: nginx-config
|
||||
readOnly: true
|
||||
- name: recipes
|
||||
image: 'vabene1111/recipes:latest'
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
volumeMounts:
|
||||
- mountPath: '/opt/recipes/mediafiles'
|
||||
name: media
|
||||
- mountPath: '/opt/recipes/staticfiles'
|
||||
name: static
|
||||
env:
|
||||
- name: DEBUG
|
||||
value: "0"
|
||||
- name: ALLOWED_HOSTS
|
||||
value: '*'
|
||||
- name: SECRET_KEY
|
||||
value: # CHANGEME
|
||||
- name: DB_ENGINE
|
||||
value: django.db.backends.postgresql_psycopg2
|
||||
- name: POSTGRES_HOST
|
||||
value: localhost
|
||||
- name: POSTGRES_PORT
|
||||
value: "5432"
|
||||
- name: POSTGRES_USER
|
||||
value: recipes
|
||||
- name: POSTGRES_DB
|
||||
value: recipes
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: # CHANGEME
|
||||
- name: recipes-db
|
||||
image: 'postgres:latest'
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
volumeMounts:
|
||||
- mountPath: '/var/lib/postgresql/data'
|
||||
name: database
|
||||
env:
|
||||
- name: POSTGRES_USER
|
||||
value: recipes
|
||||
- name: POSTGRES_DB
|
||||
value: recipes
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: # CHANGEME
|
||||
volumes:
|
||||
- name: database
|
||||
persistentVolumeClaim:
|
||||
claimName: recipes-db
|
||||
- name: media
|
||||
persistentVolumeClaim:
|
||||
claimName: recipes-media
|
||||
- name: static
|
||||
persistentVolumeClaim:
|
||||
claimName: recipes-static
|
||||
- name: nginx-config
|
||||
configMap:
|
||||
name: recipes-nginx-config
|
||||
15
docs/k8s/60-service.yaml
Normal file
15
docs/k8s/60-service.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: recipes
|
||||
labels:
|
||||
app: recipes
|
||||
spec:
|
||||
selector:
|
||||
app: recipes
|
||||
environment: production
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: http
|
||||
name: http
|
||||
protocol: TCP
|
||||
25
docs/k8s/README.md
Normal file
25
docs/k8s/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Kubernetes
|
||||
|
||||
This is a basic kubernetes setup. Please note that this does not necessarily follow Kubernetes best practices and should only used as a basis to build your own setup from!
|
||||
|
||||
## Important notes
|
||||
|
||||
State (database, static files and media files) is handled via `PersistentVolumes`.
|
||||
|
||||
Note that you will most likely have to change the `PersistentVolumes` in `30-pv.yaml`. The current setup is only usable for a single-node cluster because it uses local storage on the kubernetes worker nodes under `/data/recipes/`. It should just serve as an example.
|
||||
|
||||
Currently, the deployment in `50-deployment.yaml` just pulls the `latest` tag of all containers. In a production setup, you should set this to a fixed version!
|
||||
|
||||
See env variables tagged with `CHANGEME` in `50-deployment.yaml` and make sure to change those! A better setup would use kubernetes secrets but this is not implemented yet.
|
||||
|
||||
## Updates
|
||||
|
||||
These manifests are not tested against new versions.
|
||||
|
||||
## Apply the manifets
|
||||
|
||||
To apply the manifest with `kubectl`, use the following command:
|
||||
|
||||
```
|
||||
kubectl apply -f ./docs/k8s/
|
||||
```
|
||||
Reference in New Issue
Block a user