├── .gitignore ├── roles └── infra │ ├── files │ ├── entrypoint.sh │ ├── Dockerfile │ └── spyglass │ │ ├── index.html │ │ ├── script.js │ │ └── style.css │ ├── tasks │ ├── main.yml │ ├── teardown.yml │ ├── spyglass.yml │ └── workshop.yml │ ├── defaults │ └── main.yml │ └── templates │ └── spyglass │ └── index.html.j2 ├── setup.yml ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | local_settings 2 | ansible_gitlab.key 3 | *.retry 4 | *.htpasswd 5 | *.DS_Store 6 | .idea 7 | ansible.cfg 8 | hosts 9 | inventory -------------------------------------------------------------------------------- /roles/infra/files/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /usr/local/bin/dockerd-entrypoint.sh & 4 | /gotty --permit-write --reconnect tmux new -A -s gotty_session /bin/ash -------------------------------------------------------------------------------- /roles/infra/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Create mentor dir 4 | file: 5 | path: "{{ mentor_root }}" 6 | state: directory 7 | owner: root 8 | group: root 9 | mode: 0755 10 | 11 | - include: spyglass.yml 12 | tags: [setup] 13 | 14 | - include: workshop.yml 15 | tags: [setup] 16 | 17 | - include: teardown.yml 18 | tags: [never, teardown] 19 | 20 | -------------------------------------------------------------------------------- /roles/infra/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | mentor_root: "/srv/mentor" 4 | workshop_src: "/Users/andy/work/patroni-class/ansible/" 5 | users_num: 2 6 | tz: "Asia/Yekaterinburg" 7 | mentor_base_url: "http://{{ inventory_hostname }}" 8 | mentor_container_prefix: "mentor" 9 | mentor_user_container: "{{ mentor_container_prefix }}_dind_user" 10 | mentor_spyglass_container: "{{ mentor_container_prefix }}_spyglass" -------------------------------------------------------------------------------- /setup.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: workshop 3 | become: yes 4 | become_method: sudo 5 | gather_facts: False 6 | tasks: 7 | - name: install python 2 8 | raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal python-setuptools) 9 | 10 | - hosts: workshop 11 | become: true 12 | become_method: sudo 13 | roles: 14 | - { role: geerlingguy.docker} 15 | - { role: infra } 16 | -------------------------------------------------------------------------------- /roles/infra/tasks/teardown.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Removing user's containers 3 | docker_container: 4 | name: "{{ mentor_user_container }}{{'%d' | format(item) }}" 5 | state: absent 6 | loop: "{{ range(0, (users_num | int)) | list }}" 7 | 8 | - name: Remove spyglass container 9 | docker_container: 10 | name: "{{ mentor_spyglass_container }}" 11 | state: absent 12 | 13 | - name: Remove mentor root 14 | file: 15 | path: "{{ mentor_root }}" 16 | state: absent -------------------------------------------------------------------------------- /roles/infra/tasks/spyglass.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Create spyglass dir 4 | file: 5 | path: "{{ mentor_root }}/spyglass" 6 | state: directory 7 | owner: root 8 | group: root 9 | mode: 0755 10 | 11 | - name: Copy spyglass static 12 | synchronize: 13 | src: "spyglass/" 14 | dest: "{{ mentor_root }}/spyglass" 15 | 16 | - name: Copy spyglass templates 17 | template: 18 | src: "spyglass/index.html.j2" 19 | dest: "{{ mentor_root }}/spyglass/index.html" 20 | 21 | - name: Run spyglass container for workshop 22 | docker_container: 23 | name: "{{ mentor_spyglass_container }}" 24 | image: nginx:alpine 25 | env: 26 | volumes: 27 | - "{{ mentor_root }}/spyglass:/usr/share/nginx/html" 28 | ports: 29 | "80:80" -------------------------------------------------------------------------------- /roles/infra/files/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | RUN apk update && \ 3 | apk add --virtual build-deps make git 4 | # Build gotty 5 | RUN go env -w GO111MODULE=off && go get -d github.com/yudai/gotty && \ 6 | git -C /go/src/github.com/yudai/gotty checkout release-1.0 && \ 7 | go install github.com/yudai/gotty 8 | 9 | 10 | FROM docker:20.10-dind 11 | 12 | COPY --from=builder /go/bin/gotty /gotty 13 | RUN apk update && apk add bash htop vim iftop iotop iperf net-tools iputils postgresql-client git tmux ansible openssh-keygen openssh tzdata py-pip python2 14 | 15 | RUN pip3 install docker-compose 16 | RUN apk del libc6-compat 17 | RUN echo $'[defaults] \n\ 18 | host_key_checking = False' > /root/.ansible.cfg 19 | 20 | EXPOSE 8080 21 | COPY entrypoint.sh /usr/local/bin/ 22 | RUN chmod +x /usr/local/bin/entrypoint.sh 23 | ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] 24 | WORKDIR /workshop 25 | CMD [] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 foobar.engineering 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /roles/infra/tasks/workshop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: Copy dockerfiles for workshop image build 4 | copy: 5 | src: "{{ item }}" 6 | dest: "{{ mentor_root }}/{{ item }}" 7 | with_items: 8 | - Dockerfile 9 | - entrypoint.sh 10 | 11 | - name: Build mentor_dind workshop image 12 | docker_image: 13 | name: mentor_dind 14 | build: 15 | path: "{{ mentor_root }}" 16 | pull: false 17 | source: build 18 | 19 | - name: Create user's folders 20 | file: 21 | state: directory 22 | path: "{{ mentor_root }}/{{ 'user%d'|format(item) }}" 23 | mode: 0755 24 | loop: "{{ range(0, (users_num | int)) | list }}" 25 | 26 | - name: Push workshop code to mentor server 27 | synchronize: 28 | src: "{{ workshop_src }}" 29 | dest: "{{ mentor_root }}/{{ 'user%d'|format(item) }}" 30 | loop: "{{ range(0, (users_num | int)) | list }}" 31 | 32 | - name: Run user's containers for workshop 33 | docker_container: 34 | name: "{{ mentor_user_container }}{{'%d' | format(item) }}" 35 | image: mentor_dind 36 | privileged: true 37 | env: 38 | GOTTY_CREDENTIAL: "{{ 'user%d'|format(item) }}:{{ ('%d'|format(item)|hash('md5'))[:2] }}" 39 | TERM: 'xterm' 40 | TZ: "{{ tz }}" 41 | volumes: 42 | - "{{ mentor_root }}/{{ 'user%d'|format(item) }}:/workshop" 43 | ports: 44 | "{{'%d' | format(item + 8000)}}:8080" 45 | loop: "{{ range(0, (users_num | int)) | list }}" 46 | 47 | - name: Generate config for MultiPass 48 | set_fact: 49 | multipass: "{{ multipass|default([]) + 50 | [ 51 | {'password':('%d'|format(item)|hash('md5'))[:2], 52 | 'priority':'1', 53 | 'url': '{{ mentor_base_url }}:%d' | format(item + 8000), 54 | 'username': 'user%d'|format(item) } 55 | ] }}" 56 | loop: "{{ range(0, (users_num | int)) | list }}" 57 | 58 | - name: Here is config for MultiPass 59 | debug: 60 | msg: "{{ multipass }}" 61 | -------------------------------------------------------------------------------- /roles/infra/files/spyglass/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Mentor | Spyglass 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
Powered by foobar.engineering
24 | 27 |
28 |
29 |
30 |
31 |
32 | 35 |
36 |
37 | 40 |
41 |
42 | 45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 |
54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /roles/infra/templates/spyglass/index.html.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Mentor | Spyglass 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
Mentor is powered for you by foobar.engineering with love.
24 | 27 |
28 |
29 |
30 |
31 |
32 | 35 |
36 |
37 | 40 |
41 |
42 | 45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 |
54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /roles/infra/files/spyglass/script.js: -------------------------------------------------------------------------------- 1 | (function($){ 2 | var 3 | $doc = $(document), 4 | $win = $(window); 5 | 6 | $win.on('load', init); 7 | 8 | function init() { 9 | $doc.on('click', '.js-nav-bar-link', function(e) { 10 | var href = $(this).attr('href'); 11 | e.preventDefault(); 12 | 13 | $('.js-content-part').removeClass('active'); 14 | 15 | $(href).addClass('active'); 16 | 17 | href == '#terminals' && loadTerminals(); 18 | }); 19 | 20 | $doc.on('click', '.js-expand-terminal', function(e) { 21 | e.preventDefault(); 22 | $(this).parent().addClass('active'); 23 | }); 24 | 25 | $doc.on('click', '.js-close', function(e) { 26 | e.preventDefault(); 27 | $(this).parent().removeClass('active'); 28 | }) 29 | } 30 | 31 | function loadTerminals() { 32 | var 33 | config = getSettings(), 34 | $output = $('#terminals'), 35 | $list = $('
').addClass('terminals-list'), 36 | users = buildUsers(config); 37 | 38 | $output.html(''); 39 | 40 | users.forEach(function(user) { 41 | $list.append(renderTerminalFor(user, config)); 42 | }); 43 | 44 | $output.append($list); 45 | } 46 | 47 | function buildUsers(config) { 48 | var 49 | users = []; 50 | 51 | if (config.portsCont > 0) { 52 | for (var i = 0; i < config.portsCont; i++) { 53 | users.push(buildUser(config.portFrom*1 + i, i)); 54 | } 55 | } else { 56 | users = [config.portFrom]; 57 | } 58 | 59 | return users; 60 | } 61 | 62 | function renderTerminalFor(user, config) { 63 | var $item = $('
').addClass('terminal-list-item '); 64 | 65 | $item.append($('').attr('href', '#').addClass('close js-close').text('X')); 66 | 67 | $item.append( 68 | $('
').addClass('title js-expand-terminal') 69 | .text(buildTerminalTitle(user, config)) 70 | ); 71 | 72 | $item.append( 73 | $('
').addClass('terminal-wrapper').append( 74 | $('