Browse Source

Add local repository

master
Burathar 4 years ago
parent
commit
05161540c4
  1. 1
      biscd/biscd/__init__.py
  2. 2
      biscd/biscd/config_default.yaml
  3. 3
      biscd/biscd/froms.py
  4. 75
      biscd/biscd/models/project.py
  5. 25
      biscd/biscd/models/yaml_serializable.py
  6. 31
      biscd/biscd/routes.py
  7. 21
      biscd/biscd/templates/base.html
  8. 1
      biscd/biscd/templates/index.html
  9. 38
      biscd/biscd/templates/project.html
  10. 9
      biscd/biscd/utils.py
  11. 2
      installation-files/config_example.yaml
  12. 3
      installation-files/projects_example.yaml

1
biscd/biscd/__init__.py

@ -10,7 +10,6 @@ from flask import Flask @@ -10,7 +10,6 @@ from flask import Flask
from flask_bootstrap import Bootstrap
from flask_login import LoginManager
def read_config(application):
# pylint: disable=no-member
conf = confuse.Configuration('biscd', __name__)

2
biscd/biscd/config_default.yaml

@ -9,5 +9,7 @@ logging: @@ -9,5 +9,7 @@ logging:
log_to_stdout: true
logfile: info.log
production_path: /opt/biscd
SECRET_KEY: this-should-be-very-secret
BOOTSTRAP_SERVE_LOCAL: True

3
biscd/biscd/froms.py

@ -36,3 +36,6 @@ class NewProjectForm(FlaskForm): @@ -36,3 +36,6 @@ class NewProjectForm(FlaskForm):
project = Project.get(name=projectname.data)
if any(project):
raise ValidationError('Please use a different projectname.')
class EmptyForm(FlaskForm):
submit = SubmitField('Submit')

75
biscd/biscd/models/project.py

@ -1,4 +1,11 @@ @@ -1,4 +1,11 @@
from pathlib import Path
from shutil import rmtree
import string
import random
from werkzeug.utils import secure_filename
from git import Repo, InvalidGitRepositoryError, GitCommandError
from flask import current_app as app
from biscd import config
from .yaml_serializable import YamlSerializable
@ -14,6 +21,70 @@ class Project(YamlSerializable): @@ -14,6 +21,70 @@ class Project(YamlSerializable):
def _yaml_object_name(cls):
return 'projects'
def __init__(self, name=None, branche='master'):
def __init__(self, name=None, branch='master'):
self.name = name
self.branche = branche
self.branch = branch
self.relative_production_path = None
self.git_repo = None
@property
def absulute_path(self):
return Path(config['production_path'].get()) / self.relative_production_path
def update(self):
# pylint: disable=no-member
self._make_sure_production_path_exists()
path = self.absulute_path
app.logger.info(f"Updating {path}")
try:
repo = Repo(path)
app.logger.info('Already under git control')
except InvalidGitRepositoryError:
app.logger.info(f"Cloning repo from {self.git_repo}")
repo = Repo.clone_from(self.git_repo, path)
app.logger.info('Done!')
remotes = repo.remotes
if not any(remotes):
app.logger.error(f"Repo {path} doesn't have any remotes.")
return ["Repo doesn't have any remotes. please fix manually", 'error']
response = remotes[0].pull()
for item in response:
app.logger.debug(item)
try:
response = repo.git.checkout(self.branch)
except AttributeError:
return [f"Branch is not set for {self.name}", 'error']
except GitCommandError:
return [
f"Repo '{self.git_repo}' does not have an existing branch called {self.branch}",
'error']
return None
def delete_files(self):
self._make_sure_production_path_exists()
path = self.absulute_path
app.logger.info(f"Deleting {path}")
rmtree(path)
self.relative_production_path = None
self.save()
def _make_sure_production_path_exists(self):
prod_path_dir = Path(config['production_path'].get())
if hasattr(self, 'production_path') and self.relative_production_path:
full_path = prod_path_dir / self.relative_production_path
if not full_path.is_dir():
full_path.mkdir(parents=True)
else:
def generate_full_path():
rand_num = ''.join(random.choices(string.digits, k=6))
return prod_path_dir / (secure_filename(self.name) + rand_num)
full_path = generate_full_path()
# Make sure path is unique
while full_path.is_dir():
full_path = generate_full_path()
full_path.mkdir(parents=True)
self.relative_production_path = str(full_path.relative_to(prod_path_dir))
self.save()

25
biscd/biscd/models/yaml_serializable.py

@ -29,11 +29,14 @@ class YamlSerializable(object): @@ -29,11 +29,14 @@ class YamlSerializable(object):
def _yaml_object_name(cls):
pass
@property
def config_dict(self):
def config_dict(self, properties_only=False):
# pylint: disable=no-member
ymlserializable_dict = self.__dict__.copy()
if self.name is None:
raise TypeError("Name cannot be None")
ymlserializable_dict = {k: v for k, v in self.__dict__.items() if v is not None}
ymlserializable_dict.pop('name')
if properties_only:
return ymlserializable_dict
return {self.name: ymlserializable_dict}
def save(self, overwrite=True):
@ -41,13 +44,19 @@ class YamlSerializable(object): @@ -41,13 +44,19 @@ class YamlSerializable(object):
if self.name is None:
raise TypeError("Name cannot be None")
ymlsls = self._get_all_from_file()
if self.name in [[*ymlsl][0] for ymlsl in ymlsls]:
found_match = False
for ymlsl in ymlsls:
if self.name == [*ymlsl][0]:
found_match = True
if overwrite:
ymlsls[self.name] = self.config_dict
ymlsl[self.name] = self.config_dict(properties_only=True)
else:
raise ValueError(f"A {type(self).__name__} with name {self.name} already exists!")
else:
ymlsls.append(self.config_dict)
raise ValueError(
f"A {type(self).__name__} with name {self.name} already exists!")
break
if not found_match:
ymlsls.append(self.config_dict())
print(ymlsls)
self._save_all_to_file(ymlsls)

31
biscd/biscd/routes.py

@ -2,8 +2,9 @@ from flask import render_template, flash, abort, redirect, request, url_for @@ -2,8 +2,9 @@ from flask import render_template, flash, abort, redirect, request, url_for
from flask_login import current_user, login_user, logout_user, login_required
from werkzeug.urls import url_parse
from biscd import app
from .utils import flash_result
from .models import Project, User
from .froms import NewProjectForm, LoginForm, RegistrationForm
from .froms import NewProjectForm, LoginForm, RegistrationForm, EmptyForm
@app.route('/', methods=['GET', 'POST'])
@app.route('/index', methods=['GET', 'POST'])
@ -22,7 +23,7 @@ def login(): @@ -22,7 +23,7 @@ def login():
if form.validate_on_submit():
user = User.first(name=form.username.data)
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
flash('Invalid username or password', 'warning')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next')
@ -31,8 +32,8 @@ def login(): @@ -31,8 +32,8 @@ def login():
return redirect(next_page)
return render_template('login.html', title='Sign In', form=form)
@login_required
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
@ -46,13 +47,29 @@ def register(): @@ -46,13 +47,29 @@ def register():
user = User(name=form.username.data, email=form.email.data)
user.set_password(form.password.data)
user.save()
flash('Congratulations, you are now a registered user!')
flash('Congratulations, you are now a registered user!', 'info')
return redirect(url_for('login'))
return render_template('register.html', title='Register', form=form)
@app.route('/project/<project_url>', methods=['GET', 'POST'])
@app.route('/project/<project_name>', methods=['GET'])
@login_required
def project_dashboard(project_url):
project = Project.first_or_404(url=project_url)
def project_dashboard(project_name):
project = Project.first_or_404(name=project_name)
return render_template('project.html', project=project)
@app.route('/project/<project_name>/update', methods=['GET'])
@login_required
def project_update(project_name):
project = Project.first_or_404(name=project_name)
result = project.update()
flash_result(result)
return redirect(url_for('project_dashboard', project_name=project.name))
@app.route('/project/<project_name>/delete_files', methods=['GET'])
@login_required
def project_delete_files(project_name):
project = Project.first_or_404(name=project_name)
result = project.delete_files()
flash_result(result)
return redirect(url_for('project_dashboard', project_name=project.name))

21
biscd/biscd/templates/base.html

@ -64,10 +64,25 @@ @@ -64,10 +64,25 @@
<div class="container">
{% with messages = get_flashed_messages() %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-info" role="alert">{{ message }}</div>
{% for category, message in messages %}
<div class="row">
<div class="col-10">
{% if category == 'message' or category == 'info' %}
<div class="alert alert-dismissible alert-info">
{% elif category == 'success' %}
<div class="alert alert-dismissible alert-success">
{% elif category == 'warning' %}
<div class="alert alert-dismissible alert-warning">
{% elif category == 'error' %}
<div class="alert alert-dismissible alert-danger">
{% endif %}
<button type="button" class="close" data-dismiss="alert">&times;</button>
{{ message }}
</div>
</div>
</div>
{% endfor %}
{% endif %}
{% endwith %}

1
biscd/biscd/templates/index.html

@ -10,7 +10,6 @@ @@ -10,7 +10,6 @@
</div>
</div>
{% if project_names %}
<div class="col-lg-12">
<h2 id="typography">Your Projects</h2>
</div>

38
biscd/biscd/templates/project.html

@ -3,24 +3,38 @@ @@ -3,24 +3,38 @@
{% block app_content %}
<h1>{{ project.name }}</h1>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Branch</th>
<th scope="col">Git Repo</th>
<th scope="col">Requirements file</th>
<th scope="col">Tests</th>
<th scope="col">Url</th>
</tr>
</thead>
<row>
<a href="{{ url_for('project_update', project_name=project.name) }}">
<button class="btn btn-success">Update Repo</button>
</a>
<a href="{{ url_for('project_delete_files', project_name=project.name) }}">
<button class="btn btn-danger">Delete Local Repo</button>
</a>
<br><br>
</row>
<div class="table-responsive col-lg-6">
<table class="table table-hover">
<tbody>
<tr>
<th>Branch</th>
<td>{{ project.branch }}</td>
</tr>
<tr>
<th>Git Repo</th>
<td>{{ project.git_repo }}</td>
</tr>
<tr>
<th>Requirements file</th>
<td>{{ project.requirements_file }}</td>
</tr>
<tr>
<th>Tests</th>
<td>{{ project.tests }}</td>
<td>{{ project.url }}</td>
</tr>
<tr>
<th>Production Path</th>
<td>{{ project.relative_production_path }}</td>
</tr>
</tbody>
</table>

9
biscd/biscd/utils.py

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
from datetime import datetime
from flask import request
from flask import request, flash
def get_timestamp():
return datetime.now().astimezone().isoformat(timespec='milliseconds')
@ -10,6 +10,13 @@ def get_remote_addr(): @@ -10,6 +10,13 @@ def get_remote_addr():
#print(f"HTTP_X_FORWARDED_FOR: {request.environ.get('HTTP_X_FORWARDED_FOR')}")
return request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr)
def flash_result(*result):
if result[0] is not None:
if isinstance(result, list):
flash(*result)
else:
flash(result)
class ObjectView(object):
def __init__(self, d):
self.__dict__ = d

2
installation-files/config_example.yaml

@ -9,5 +9,7 @@ logging: @@ -9,5 +9,7 @@ logging:
log_to_stdout: true
logfile: info.log
production_path: /opt/biscd
SECRET_KEY: this-should-be-very-secret
BOOTSTRAP_SERVE_LOCAL: True

3
installation-files/projects_example.yaml

@ -5,11 +5,10 @@ projects: @@ -5,11 +5,10 @@ projects:
requirements_file: requirements.txt
secret: thisissecret
tests: tests.py
url: thehunt
- Foo:
branch: master
git_repo: https://git.sciuro.org/Burathar/The-Hunt
relative_production_path: Foo045878
requirements_file: requirements.txt
secret: thisissecret
tests: tests.py
url: bar
Loading…
Cancel
Save