mirror of https://github.com/JohnHammond/CTFd.git
Refactor Containers into a plugin (#348)
* Removing Containers code * Closes #301selenium-screenshot-testing
parent
92b7ca06ca
commit
2e41886591
|
@ -113,7 +113,7 @@ def create_app(config='CTFd.config.Config'):
|
|||
from CTFd.challenges import challenges
|
||||
from CTFd.scoreboard import scoreboard
|
||||
from CTFd.auth import auth
|
||||
from CTFd.admin import admin, admin_statistics, admin_challenges, admin_pages, admin_scoreboard, admin_containers, admin_keys, admin_teams
|
||||
from CTFd.admin import admin, admin_statistics, admin_challenges, admin_pages, admin_scoreboard, admin_keys, admin_teams
|
||||
from CTFd.utils import init_utils, init_errors, init_logs
|
||||
|
||||
init_utils(app)
|
||||
|
@ -131,7 +131,6 @@ def create_app(config='CTFd.config.Config'):
|
|||
app.register_blueprint(admin_teams)
|
||||
app.register_blueprint(admin_scoreboard)
|
||||
app.register_blueprint(admin_keys)
|
||||
app.register_blueprint(admin_containers)
|
||||
app.register_blueprint(admin_pages)
|
||||
|
||||
from CTFd.plugins import init_plugins
|
||||
|
|
|
@ -10,7 +10,7 @@ from sqlalchemy.sql import not_
|
|||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from CTFd.utils import admins_only, is_admin, cache, export_ctf, import_ctf
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.scoreboard import get_standings
|
||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||
|
||||
|
@ -18,7 +18,6 @@ from CTFd.admin.statistics import admin_statistics
|
|||
from CTFd.admin.challenges import admin_challenges
|
||||
from CTFd.admin.scoreboard import admin_scoreboard
|
||||
from CTFd.admin.pages import admin_pages
|
||||
from CTFd.admin.containers import admin_containers
|
||||
from CTFd.admin.keys import admin_keys
|
||||
from CTFd.admin.teams import admin_teams
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
from CTFd.utils import admins_only, is_admin, cache
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, Hints, Unlocks, DatabaseError
|
||||
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, Hints, Unlocks, DatabaseError
|
||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||
from CTFd.plugins.challenges import get_chal_class, CHALLENGE_CLASSES
|
||||
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
from CTFd.utils import admins_only, is_admin, cache
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
|
||||
from CTFd import utils
|
||||
|
||||
admin_containers = Blueprint('admin_containers', __name__)
|
||||
|
||||
|
||||
@admin_containers.route('/admin/containers', methods=['GET'])
|
||||
@admins_only
|
||||
def list_container():
|
||||
containers = Containers.query.all()
|
||||
for c in containers:
|
||||
c.status = utils.container_status(c.name)
|
||||
c.ports = ', '.join(utils.container_ports(c.name, verbose=True))
|
||||
return render_template('admin/containers.html', containers=containers)
|
||||
|
||||
|
||||
@admin_containers.route('/admin/containers/<int:container_id>/stop', methods=['POST'])
|
||||
@admins_only
|
||||
def stop_container(container_id):
|
||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
||||
if utils.container_stop(container.name):
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
|
||||
|
||||
@admin_containers.route('/admin/containers/<int:container_id>/start', methods=['POST'])
|
||||
@admins_only
|
||||
def run_container(container_id):
|
||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
||||
if utils.container_status(container.name) == 'missing':
|
||||
if utils.run_image(container.name):
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
else:
|
||||
if utils.container_start(container.name):
|
||||
return '1'
|
||||
else:
|
||||
return '0'
|
||||
|
||||
|
||||
@admin_containers.route('/admin/containers/<int:container_id>/delete', methods=['POST'])
|
||||
@admins_only
|
||||
def delete_container(container_id):
|
||||
container = Containers.query.filter_by(id=container_id).first_or_404()
|
||||
if utils.delete_image(container.name):
|
||||
db.session.delete(container)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
return '1'
|
||||
|
||||
|
||||
@admin_containers.route('/admin/containers/new', methods=['POST'])
|
||||
@admins_only
|
||||
def new_container():
|
||||
name = request.form.get('name')
|
||||
if not set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'):
|
||||
return redirect(url_for('admin_containers.list_container'))
|
||||
buildfile = request.form.get('buildfile')
|
||||
files = request.files.getlist('files[]')
|
||||
utils.create_image(name=name, buildfile=buildfile, files=files)
|
||||
utils.run_image(name)
|
||||
return redirect(url_for('admin_containers.list_container'))
|
|
@ -1,6 +1,6 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
from CTFd.utils import admins_only, is_admin, cache
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.plugins.keys import get_key_class, KEY_CLASSES
|
||||
|
||||
from CTFd import utils
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
from CTFd.utils import admins_only, is_admin, cache
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
|
||||
from CTFd import utils
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
from CTFd.utils import admins_only, is_admin, cache
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.scoreboard import get_standings
|
||||
|
||||
from CTFd import utils
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
from CTFd.utils import admins_only, is_admin, cache
|
||||
from CTFd.models import db, Teams, Solves, Awards, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.models import db, Teams, Solves, Awards, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
|
||||
from CTFd import utils
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
|
||||
from CTFd.utils import admins_only, is_admin, cache
|
||||
from CTFd.models import db, Teams, Solves, Awards, Unlocks, Containers, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from CTFd.models import db, Teams, Solves, Awards, Unlocks, Challenges, WrongKeys, Keys, Tags, Files, Tracking, Pages, Config, DatabaseError
|
||||
from passlib.hash import bcrypt_sha256
|
||||
from sqlalchemy.sql import not_
|
||||
|
||||
|
|
|
@ -41,19 +41,6 @@ class Pages(db.Model):
|
|||
return "<Pages route {0}>".format(self.route)
|
||||
|
||||
|
||||
class Containers(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(80))
|
||||
buildfile = db.Column(db.Text)
|
||||
|
||||
def __init__(self, name, buildfile):
|
||||
self.name = name
|
||||
self.buildfile = buildfile
|
||||
|
||||
def __repr__(self):
|
||||
return "<Container ID:(0) {1}>".format(self.id, self.name)
|
||||
|
||||
|
||||
class Challenges(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(80))
|
||||
|
|
|
@ -47,9 +47,6 @@
|
|||
</li>
|
||||
<li><a href="{{ request.script_root }}/admin/teams">Teams</a></li>
|
||||
<li><a href="{{ request.script_root }}/admin/scoreboard">Scoreboard</a></li>
|
||||
{% if can_create_container() %}
|
||||
<li><a href="{{ request.script_root }}/admin/containers">Containers</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ request.script_root }}/admin/chals">Challenges</a></li>
|
||||
<li><a href="{{ request.script_root }}/admin/statistics">Statistics</a></li>
|
||||
<li><a href="{{ request.script_root }}/admin/config">Config</a></li>
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
{% extends "admin/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="modal fade" id="create-container-modal" tabindex="-1" role="dialog" aria-labelledby="container-modal-label">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
|
||||
aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="container-modal-label">Create Container</h4>
|
||||
</div>
|
||||
<form method="POST" action="{{ request.script_root }}/admin/containers/new" enctype="multipart/form-data">
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" class="form-control" name="name" placeholder="Enter container name">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="buildfile-editor" class="control-label">Build File</label>
|
||||
<textarea id="buildfile-editor" class="form-control" name="buildfile" rows="10" placeholder="Enter container build file"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="container-files">Associated Files
|
||||
<i class="fa fa-question-circle gray-text" data-toggle="tooltip" data-placement="right" title="These files are uploaded alongside your buildfile"></i>
|
||||
</label>
|
||||
<input type="file" name="files[]" id="container-files" multiple>
|
||||
<sub class="help-block">Attach multiple files using Control+Click or Cmd+Click.</sub>
|
||||
</div>
|
||||
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-primary">Create</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="confirm" class="modal fade" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="text-center"><span id="confirm-container-title"></span> Container</h2>
|
||||
</div>
|
||||
<div class="modal-body" style="height:110px">
|
||||
<div class="row-fluid">
|
||||
<div class="col-md-12">
|
||||
<form method="POST">
|
||||
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
|
||||
<div class="small-6 small-centered text-center columns">
|
||||
<p>Are you sure you want to <span id="confirm-container-method"></span> <strong id="confirm-container-name"></strong>?</p>
|
||||
<button type="button" data-dismiss="modal" class="btn btn-theme btn-outlined">No</button>
|
||||
<button type="button" id="confirm-container" class="btn btn-theme btn-outlined">Yes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<br>
|
||||
<div style="text-align:center">
|
||||
<h1>Containers</h1>
|
||||
<button class="btn btn-theme btn-outlined create-challenge" data-toggle="modal" data-target="#create-container-modal">
|
||||
New Container
|
||||
</button>
|
||||
</div>
|
||||
<br>
|
||||
{% if containers %}
|
||||
<table id="teamsboard">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="text-center"><strong>Status</strong>
|
||||
</td>
|
||||
<td class="text-center"><strong>Name</strong>
|
||||
</td>
|
||||
<td class="text-center"><strong>Ports</strong>
|
||||
</td>
|
||||
<td class="text-center"><strong>Settings</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in containers %}
|
||||
<tr>
|
||||
<td class="text-center">{{ c.status }}</td>
|
||||
<td class="text-center container_item" id="{{ c.id }}">{{ c.name }}</td>
|
||||
<td class="text-center">{{ c.ports }}</td>
|
||||
<td class="text-center">
|
||||
<span>
|
||||
{% if c.status != 'running' %}
|
||||
<i class="fa fa-play"></i>
|
||||
{% else %}
|
||||
<i class="fa fa-stop"></i>
|
||||
{% endif %}
|
||||
<i class="fa fa-times"></i>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
|
||||
function load_confirm_modal(title, url, container_name){
|
||||
var modal = $('#confirm')
|
||||
modal.find('#confirm-container-name').text(container_name)
|
||||
modal.find('#confirm-container-title').text(title)
|
||||
modal.find('#confirm-container-method').text(title.toLowerCase())
|
||||
$('#confirm form').attr('action', url);
|
||||
$('#confirm').modal('show');
|
||||
}
|
||||
|
||||
$('#confirm-container').click(function(e){
|
||||
e.preventDefault();
|
||||
var id = $('#confirm input[name="id"]').val()
|
||||
var user_data = $('#confirm form').serializeArray()
|
||||
$.post($('#confirm form').attr('action'), $('#confirm form').serialize(), function(data){
|
||||
var data = $.parseJSON(JSON.stringify(data))
|
||||
if (data == "1"){
|
||||
location.reload()
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
$('.fa-times').click(function(){
|
||||
var elem = $(this).parent().parent().parent().find('.container_item');
|
||||
var container = elem.attr('id');
|
||||
var container_name = elem.text().trim();
|
||||
load_confirm_modal('Delete', '/admin/containers/'+container+'/delete', container_name)
|
||||
});
|
||||
|
||||
$('.fa-play').click(function(){
|
||||
var elem = $(this).parent().parent().parent().find('.container_item');
|
||||
var container = elem.attr('id');
|
||||
var container_name = elem.text().trim();
|
||||
load_confirm_modal('Start', '/admin/containers/'+container+'/start', container_name)
|
||||
});
|
||||
|
||||
$('.fa-stop').click(function(){
|
||||
var elem = $(this).parent().parent().parent().find('.container_item');
|
||||
var container = elem.attr('id');
|
||||
var container_name = elem.text().trim();
|
||||
load_confirm_modal('Stop', '/admin/containers/'+container+'/stop', container_name)
|
||||
});
|
||||
|
||||
$(document).ready(function(){
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -47,9 +47,6 @@
|
|||
</li>
|
||||
<li><a href="{{ request.script_root }}/admin/teams">Teams</a></li>
|
||||
<li><a href="{{ request.script_root }}/admin/scoreboard">Scoreboard</a></li>
|
||||
{% if can_create_container() %}
|
||||
<li><a href="{{ request.script_root }}/admin/containers">Containers</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{{ request.script_root }}/admin/chals">Challenges</a></li>
|
||||
<li><a href="{{ request.script_root }}/admin/statistics">Statistics</a></li>
|
||||
<li><a href="{{ request.script_root }}/admin/config">Config</a></li>
|
||||
|
|
149
CTFd/utils.py
149
CTFd/utils.py
|
@ -13,7 +13,6 @@ import shutil
|
|||
import six
|
||||
import smtplib
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
|
@ -29,7 +28,7 @@ from itsdangerous import TimedSerializer, BadTimeSignature, Signer, BadSignature
|
|||
from six.moves.urllib.parse import urlparse, urljoin, quote, unquote
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from CTFd.models import db, WrongKeys, Pages, Config, Tracking, Teams, Files, Containers, ip2long, long2ip
|
||||
from CTFd.models import db, WrongKeys, Pages, Config, Tracking, Teams, Files, ip2long, long2ip
|
||||
|
||||
cache = Cache()
|
||||
migrate = Migrate()
|
||||
|
@ -105,7 +104,6 @@ def init_utils(app):
|
|||
app.jinja_env.globals.update(can_send_mail=can_send_mail)
|
||||
app.jinja_env.globals.update(ctf_name=ctf_name)
|
||||
app.jinja_env.globals.update(ctf_theme=ctf_theme)
|
||||
app.jinja_env.globals.update(can_create_container=can_create_container)
|
||||
app.jinja_env.globals.update(get_configurable_plugins=get_configurable_plugins)
|
||||
app.jinja_env.globals.update(get_config=get_config)
|
||||
app.jinja_env.globals.update(hide_scores=hide_scores)
|
||||
|
@ -557,136 +555,6 @@ def base64decode(s, urldecode=False):
|
|||
return decoded
|
||||
|
||||
|
||||
@cache.memoize()
|
||||
def can_create_container():
|
||||
try:
|
||||
subprocess.check_output(['docker', 'version'])
|
||||
return True
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
return False
|
||||
|
||||
|
||||
def is_port_free(port):
|
||||
s = socket.socket()
|
||||
result = s.connect_ex(('127.0.0.1', port))
|
||||
if result == 0:
|
||||
s.close()
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def create_image(name, buildfile, files):
|
||||
if not can_create_container():
|
||||
return False
|
||||
folder = tempfile.mkdtemp(prefix='ctfd')
|
||||
tmpfile = tempfile.NamedTemporaryFile(dir=folder, delete=False)
|
||||
tmpfile.write(buildfile)
|
||||
tmpfile.close()
|
||||
|
||||
for f in files:
|
||||
if f.filename.strip():
|
||||
filename = os.path.basename(f.filename)
|
||||
f.save(os.path.join(folder, filename))
|
||||
# repository name component must match "[a-z0-9](?:-*[a-z0-9])*(?:[._][a-z0-9](?:-*[a-z0-9])*)*"
|
||||
# docker build -f tmpfile.name -t name
|
||||
try:
|
||||
cmd = ['docker', 'build', '-f', tmpfile.name, '-t', name, folder]
|
||||
print(cmd)
|
||||
subprocess.call(cmd)
|
||||
container = Containers(name, buildfile)
|
||||
db.session.add(container)
|
||||
db.session.commit()
|
||||
db.session.close()
|
||||
rmdir(folder)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def delete_image(name):
|
||||
try:
|
||||
subprocess.call(['docker', 'rm', name])
|
||||
subprocess.call(['docker', 'rmi', name])
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def run_image(name):
|
||||
try:
|
||||
info = json.loads(subprocess.check_output(['docker', 'inspect', '--type=image', name]))
|
||||
|
||||
try:
|
||||
ports_asked = info[0]['Config']['ExposedPorts'].keys()
|
||||
ports_asked = [int(re.sub('[A-Za-z/]+', '', port)) for port in ports_asked]
|
||||
except KeyError:
|
||||
ports_asked = []
|
||||
|
||||
cmd = ['docker', 'run', '-d']
|
||||
ports_used = []
|
||||
for port in ports_asked:
|
||||
if is_port_free(port):
|
||||
cmd.append('-p')
|
||||
cmd.append('{}:{}'.format(port, port))
|
||||
else:
|
||||
cmd.append('-p')
|
||||
ports_used.append('{}'.format(port))
|
||||
cmd += ['--name', name, name]
|
||||
print(cmd)
|
||||
subprocess.call(cmd)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def container_start(name):
|
||||
try:
|
||||
cmd = ['docker', 'start', name]
|
||||
subprocess.call(cmd)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def container_stop(name):
|
||||
try:
|
||||
cmd = ['docker', 'stop', name]
|
||||
subprocess.call(cmd)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def container_status(name):
|
||||
try:
|
||||
data = json.loads(subprocess.check_output(['docker', 'inspect', '--type=container', name]))
|
||||
status = data[0]["State"]["Status"]
|
||||
return status
|
||||
except subprocess.CalledProcessError:
|
||||
return 'missing'
|
||||
|
||||
|
||||
def container_ports(name, verbose=False):
|
||||
try:
|
||||
info = json.loads(subprocess.check_output(['docker', 'inspect', '--type=container', name]))
|
||||
if verbose:
|
||||
ports = info[0]["NetworkSettings"]["Ports"]
|
||||
if not ports:
|
||||
return []
|
||||
final = []
|
||||
for port in ports.keys():
|
||||
final.append("".join([ports[port][0]["HostPort"], '->', port]))
|
||||
return final
|
||||
else:
|
||||
ports = info[0]['Config']['ExposedPorts'].keys()
|
||||
if not ports:
|
||||
return []
|
||||
ports = [int(re.sub('[A-Za-z/]+', '', port)) for port in ports]
|
||||
return ports
|
||||
except subprocess.CalledProcessError:
|
||||
return []
|
||||
|
||||
|
||||
def export_ctf(segments=None):
|
||||
db = dataset.connect(get_config('SQLALCHEMY_DATABASE_URI'))
|
||||
if segments is None:
|
||||
|
@ -714,7 +582,6 @@ def export_ctf(segments=None):
|
|||
'alembic_version',
|
||||
'config',
|
||||
'pages',
|
||||
'containers',
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -775,7 +642,6 @@ def import_ctf(backup, segments=None, erase=False):
|
|||
'alembic_version',
|
||||
'config',
|
||||
'pages',
|
||||
'containers',
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -812,19 +678,6 @@ def import_ctf(backup, segments=None, erase=False):
|
|||
db.session.add(page)
|
||||
db.session.commit()
|
||||
|
||||
elif item == 'containers':
|
||||
saved = json.loads(data)
|
||||
for entry in saved['results']:
|
||||
name = entry['name']
|
||||
buildfile = entry['buildfile']
|
||||
container = Containers.query.filter_by(name=name).first()
|
||||
if container:
|
||||
container.buildfile = buildfile
|
||||
else:
|
||||
container = Containers(name, buildfile)
|
||||
db.session.add(container)
|
||||
db.session.commit()
|
||||
|
||||
for segment in segments:
|
||||
group = groups[segment]
|
||||
for item in group:
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
"""Removes containers table in favor of using a plugin to manage Containers
|
||||
|
||||
Revision ID: cbf5620f8e15
|
||||
Revises: 1ec4a28fe0ff
|
||||
Create Date: 2017-08-12 04:11:45.970248
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'cbf5620f8e15'
|
||||
down_revision = '1ec4a28fe0ff'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('containers')
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('containers',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('name', sa.String(length=80), nullable=True),
|
||||
sa.Column('buildfile', sa.Text(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
|
@ -44,16 +44,6 @@ def test_admin_scoreboard():
|
|||
destroy_ctfd(app)
|
||||
|
||||
|
||||
def test_admin_containers():
|
||||
"""Does admin containers return a 200 by default"""
|
||||
app = create_ctfd()
|
||||
with app.app_context():
|
||||
client = login_as_user(app, name="admin", password="password")
|
||||
r = client.get('/admin/containers')
|
||||
assert r.status_code == 200
|
||||
destroy_ctfd(app)
|
||||
|
||||
|
||||
def test_admin_chals():
|
||||
"""Does admin chals return a 200 by default"""
|
||||
app = create_ctfd()
|
||||
|
|
Loading…
Reference in New Issue