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. 29
      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
from flask_bootstrap import Bootstrap from flask_bootstrap import Bootstrap
from flask_login import LoginManager from flask_login import LoginManager
def read_config(application): def read_config(application):
# pylint: disable=no-member # pylint: disable=no-member
conf = confuse.Configuration('biscd', __name__) conf = confuse.Configuration('biscd', __name__)

2
biscd/biscd/config_default.yaml

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

3
biscd/biscd/froms.py

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

75
biscd/biscd/models/project.py

@ -1,4 +1,11 @@
from pathlib import Path 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 biscd import config
from .yaml_serializable import YamlSerializable from .yaml_serializable import YamlSerializable
@ -14,6 +21,70 @@ class Project(YamlSerializable):
def _yaml_object_name(cls): def _yaml_object_name(cls):
return 'projects' return 'projects'
def __init__(self, name=None, branche='master'): def __init__(self, name=None, branch='master'):
self.name = name 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()

29
biscd/biscd/models/yaml_serializable.py

@ -29,11 +29,14 @@ class YamlSerializable(object):
def _yaml_object_name(cls): def _yaml_object_name(cls):
pass pass
@property def config_dict(self, properties_only=False):
def config_dict(self):
# pylint: disable=no-member # 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') ymlserializable_dict.pop('name')
if properties_only:
return ymlserializable_dict
return {self.name: ymlserializable_dict} return {self.name: ymlserializable_dict}
def save(self, overwrite=True): def save(self, overwrite=True):
@ -41,13 +44,19 @@ class YamlSerializable(object):
if self.name is None: if self.name is None:
raise TypeError("Name cannot be None") raise TypeError("Name cannot be None")
ymlsls = self._get_all_from_file() ymlsls = self._get_all_from_file()
if self.name in [[*ymlsl][0] for ymlsl in ymlsls]: found_match = False
if overwrite: for ymlsl in ymlsls:
ymlsls[self.name] = self.config_dict if self.name == [*ymlsl][0]:
else: found_match = True
raise ValueError(f"A {type(self).__name__} with name {self.name} already exists!") if overwrite:
else: ymlsl[self.name] = self.config_dict(properties_only=True)
ymlsls.append(self.config_dict) else:
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) print(ymlsls)
self._save_all_to_file(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
from flask_login import current_user, login_user, logout_user, login_required from flask_login import current_user, login_user, logout_user, login_required
from werkzeug.urls import url_parse from werkzeug.urls import url_parse
from biscd import app from biscd import app
from .utils import flash_result
from .models import Project, User 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('/', methods=['GET', 'POST'])
@app.route('/index', methods=['GET', 'POST']) @app.route('/index', methods=['GET', 'POST'])
@ -22,7 +23,7 @@ def login():
if form.validate_on_submit(): if form.validate_on_submit():
user = User.first(name=form.username.data) user = User.first(name=form.username.data)
if user is None or not user.check_password(form.password.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')) return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data) login_user(user, remember=form.remember_me.data)
next_page = request.args.get('next') next_page = request.args.get('next')
@ -31,8 +32,8 @@ def login():
return redirect(next_page) return redirect(next_page)
return render_template('login.html', title='Sign In', form=form) return render_template('login.html', title='Sign In', form=form)
@login_required
@app.route('/logout') @app.route('/logout')
@login_required
def logout(): def logout():
logout_user() logout_user()
return redirect(url_for('index')) return redirect(url_for('index'))
@ -46,13 +47,29 @@ def register():
user = User(name=form.username.data, email=form.email.data) user = User(name=form.username.data, email=form.email.data)
user.set_password(form.password.data) user.set_password(form.password.data)
user.save() 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 redirect(url_for('login'))
return render_template('register.html', title='Register', form=form) 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 @login_required
def project_dashboard(project_url): def project_dashboard(project_name):
project = Project.first_or_404(url=project_url) project = Project.first_or_404(name=project_name)
return render_template('project.html', project=project) 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 @@
<div class="container"> <div class="container">
{% with messages = get_flashed_messages() %} {% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %} {% if messages %}
{% for message in messages %} {% for category, message in messages %}
<div class="alert alert-info" role="alert">{{ message }}</div> <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 %} {% endfor %}
{% endif %} {% endif %}
{% endwith %} {% endwith %}

1
biscd/biscd/templates/index.html

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

38
biscd/biscd/templates/project.html

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

9
biscd/biscd/utils.py

@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from flask import request from flask import request, flash
def get_timestamp(): def get_timestamp():
return datetime.now().astimezone().isoformat(timespec='milliseconds') return datetime.now().astimezone().isoformat(timespec='milliseconds')
@ -10,6 +10,13 @@ def get_remote_addr():
#print(f"HTTP_X_FORWARDED_FOR: {request.environ.get('HTTP_X_FORWARDED_FOR')}") #print(f"HTTP_X_FORWARDED_FOR: {request.environ.get('HTTP_X_FORWARDED_FOR')}")
return request.environ.get('HTTP_X_FORWARDED_FOR', request.remote_addr) 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): class ObjectView(object):
def __init__(self, d): def __init__(self, d):
self.__dict__ = d self.__dict__ = d

2
installation-files/config_example.yaml

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

3
installation-files/projects_example.yaml

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