├── docker_tutorial
├── tests
│ ├── __init__.py
│ └── test_views.py
├── migrations
│ ├── __init__.py
│ ├── 0001_initial.py
│ ├── 0004_auto__del_field_tutorialuser_http_accept_encoding__add_field_tutorialu.py
│ ├── 0002_auto__add_subscriber__add_unique_subscriber_email_from_level__add_dock.py
│ ├── 0005_auto__chg_field_tutorialuser_http_user_agent__chg_field_tutorialuser_h.py
│ └── 0003_auto__add_field_tutorialuser_http_user_agent__add_field_tutorialuser_h.py
├── __init__.py
├── static
│ ├── img
│ │ ├── email.png
│ │ ├── favicon.png
│ │ ├── twitter.png
│ │ ├── facebook.png
│ │ ├── docker-letters.png
│ │ ├── docker-topicon.png
│ │ ├── fullscreen_exit_32x32.png
│ │ └── circle-green.svg
│ ├── lib
│ │ ├── js
│ │ │ └── jquery.mousewheel-min.js
│ │ └── css
│ │ │ └── jquery.terminal.css
│ ├── css
│ │ ├── tutorial-style.css
│ │ └── tutorial-style.less
│ └── js
│ │ ├── steps.js
│ │ ├── steps.coffee
│ │ └── terminal.coffee
├── templates
│ └── tutorial
│ │ ├── testpage.html
│ │ ├── stats.html
│ │ └── snippet.html
├── urls.py
├── utils.py
├── admin.py
├── models.py
└── views.py
├── dotcloud.yml
├── dockerfile_tutorial
├── __init__.py
├── templates
│ └── dockerfile
│ │ ├── _dockerfile.html
│ │ ├── introduction.html
│ │ ├── level2.html
│ │ └── level1.html
├── static
│ ├── css
│ │ └── dockerfile_tutorial.css
│ └── js
│ │ ├── dockerfile_tutorial.js
│ │ ├── jquery.cookie.js
│ │ ├── dockerfile_tutorial_level1.js
│ │ └── dockerfile_tutorial_level2.js
└── urls.py
├── requirements.txt
├── CHANGES.txt
├── .gitignore
├── MANIFEST.in
├── setup.py
├── runtests.py
└── README.md
/docker_tutorial/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docker_tutorial/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dotcloud.yml:
--------------------------------------------------------------------------------
1 | www:
2 | type: static
3 |
4 |
--------------------------------------------------------------------------------
/docker_tutorial/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'thatcher'
2 |
--------------------------------------------------------------------------------
/dockerfile_tutorial/__init__.py:
--------------------------------------------------------------------------------
1 | __author__ = 'thatcher'
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django>=1.6,<1.7
2 | South>=0.8,<0.9
3 |
4 |
--------------------------------------------------------------------------------
/CHANGES.txt:
--------------------------------------------------------------------------------
1 | 0.1, Aug 13 2013 -- Initial release
2 | The initial release of the Docker tutorial
3 |
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .coverage
3 | *.pyc
4 | DockerTutorial.egg-info
5 | MANIFEST
6 | dist/
7 | htmlcov
8 |
--------------------------------------------------------------------------------
/docker_tutorial/static/img/email.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhrp/docker-tutorial/HEAD/docker_tutorial/static/img/email.png
--------------------------------------------------------------------------------
/docker_tutorial/static/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhrp/docker-tutorial/HEAD/docker_tutorial/static/img/favicon.png
--------------------------------------------------------------------------------
/docker_tutorial/static/img/twitter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhrp/docker-tutorial/HEAD/docker_tutorial/static/img/twitter.png
--------------------------------------------------------------------------------
/docker_tutorial/static/img/facebook.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhrp/docker-tutorial/HEAD/docker_tutorial/static/img/facebook.png
--------------------------------------------------------------------------------
/docker_tutorial/static/img/docker-letters.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhrp/docker-tutorial/HEAD/docker_tutorial/static/img/docker-letters.png
--------------------------------------------------------------------------------
/docker_tutorial/static/img/docker-topicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhrp/docker-tutorial/HEAD/docker_tutorial/static/img/docker-topicon.png
--------------------------------------------------------------------------------
/docker_tutorial/static/img/fullscreen_exit_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dhrp/docker-tutorial/HEAD/docker_tutorial/static/img/fullscreen_exit_32x32.png
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.txt
2 | include README.md
3 | recursive-include docker_tutorial/static *
4 | recursive-include docker_tutorial/templates *
5 |
6 | recursive-include dockerfile_tutorial/static *
7 | recursive-include dockerfile_tutorial/templates *
8 |
9 |
--------------------------------------------------------------------------------
/docker_tutorial/static/img/circle-green.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 | 1
6 |
--------------------------------------------------------------------------------
/docker_tutorial/templates/tutorial/testpage.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Docker tutorial testpage
5 |
6 |
7 |
8 | {% include 'tutorial/snippet.html' %}
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/dockerfile_tutorial/templates/dockerfile/_dockerfile.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block page_styles %}
4 |
5 | {% endblock page_styles %}
6 | {% block page_js %}
7 |
8 |
9 | {% endblock page_js %}
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docker_tutorial/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import patterns, url
2 |
3 | from . import views
4 |
5 |
6 | urlpatterns = patterns('',
7 | url(r'^$', views.testpage, name='tutorial_testpage'),
8 | url(r'^api/$', views.api, name='tutorial_api'),
9 | url(r'^api/dockerfile_event/$', views.dockerfile_event, name='dockerfile_event'),
10 | url(r'^api/subscribe/$', views.subscribe, name='subscribe'),
11 | url(r'^api/metrics/$', views.get_metrics, name='get_metrics'),
12 | url(r'^stats/$', views.stats, name='stats'),
13 | )
14 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from setuptools import setup
3 | from setuptools import find_packages
4 |
5 | setup(
6 | name='docker-tutorial',
7 | version='0.2.1',
8 | author=u'Thatcher Peskens',
9 | author_email='thatcher@dotcloud.com',
10 | packages=find_packages(),
11 | url='https://github.com/dhrp/docker-tutorial/',
12 | license='To be determined',
13 | description='An interactive learning environment to get familiar with the Docker cli',
14 | long_description=open('README.md').read(),
15 | include_package_data=True,
16 | )
17 |
--------------------------------------------------------------------------------
/docker_tutorial/tests/test_views.py:
--------------------------------------------------------------------------------
1 | from django.core.urlresolvers import reverse
2 | from django.test import TestCase
3 |
4 |
5 | class WhenGettingTheTutorialAPI(TestCase):
6 |
7 | def setUp(self):
8 | self.response = self.client.get(reverse("tutorial_api"))
9 |
10 | def test_that_the_response_is_successful(self):
11 | self.assertEqual(self.response.status_code, 200)
12 |
13 |
14 | class WhenPostingToTheAPI(TestCase):
15 |
16 | def setUp(self):
17 | self.response = self.client.post(reverse("tutorial_api"))
18 |
19 | def test_that_the_response_is_successful(self):
20 | self.assertEqual(self.response.status_code, 200)
21 |
--------------------------------------------------------------------------------
/runtests.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from django.conf import settings
4 |
5 |
6 | settings.configure(
7 | DEBUG=True,
8 | DATABASES={
9 | 'default': {
10 | 'ENGINE': 'django.db.backends.sqlite3',
11 | }
12 | },
13 | ROOT_URLCONF='docker_tutorial.urls',
14 | INSTALLED_APPS=(
15 | 'django.contrib.auth',
16 | 'django.contrib.contenttypes',
17 | 'django.contrib.sessions',
18 | 'django.contrib.admin',
19 | 'docker_tutorial',),
20 | MIDDLEWARE_CLASSES=(
21 | 'django.contrib.sessions.middleware.SessionMiddleware',
22 | ))
23 |
24 | from django.test.runner import DiscoverRunner
25 | test_runner = DiscoverRunner(verbosity=1)
26 | failures = test_runner.run_tests(['docker_tutorial', ])
27 | if failures:
28 | sys.exit(failures)
29 |
--------------------------------------------------------------------------------
/docker_tutorial/templates/tutorial/stats.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Docker tutorial stats
5 |
6 |
7 |
8 |
9 |
10 | General numbers
11 |
12 | {{ users.started }} sessions
13 | {{ users.completed }} completed
14 |
15 |
16 |
17 |
18 | How far did people get?
19 |
20 | {% for question, number in answered.items %}
21 | {{ number }}
22 | {% endfor %}
23 |
24 |
25 |
26 |
27 | How many people check for the answer, and which?
28 |
29 | {% for question, number in peeks.items %}
30 | {{ number }}
31 | {% endfor %}
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/dockerfile_tutorial/static/css/dockerfile_tutorial.css:
--------------------------------------------------------------------------------
1 | pre {
2 | background-color: #F5F5F5;
3 | border: 1px solid rgba(0, 0, 0, 0.15);
4 | border-radius: 4px 4px 4px 4px;
5 | display: block;
6 | font-size: 13px;
7 | line-height: 20px;
8 | margin: 0 0 10px;
9 | padding: 9.5px;
10 | white-space: pre-wrap;
11 | word-break: break-all;
12 | word-wrap: break-word;
13 | }
14 | code, pre {
15 | border-radius: 3px 3px 3px 3px;
16 | color: #333333;
17 | font-family: Monaco,Menlo,Consolas,"Courier New",monospace;
18 | font-size: 12px;
19 | padding: 0 3px 2px;
20 | }
21 |
22 | .terminal {
23 | color: #AAAAAA;
24 | background-color: black;
25 | }
26 |
27 | .terminal span.command {
28 | color: white;
29 | font-weight: bold;
30 | }
31 |
32 | .error_input {
33 | border-color: #B94A48 !important;
34 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset !important;
35 | }
--------------------------------------------------------------------------------
/dockerfile_tutorial/urls.py:
--------------------------------------------------------------------------------
1 | from django.conf.urls import patterns, include, url
2 | from django.views.generic import TemplateView
3 | from django.core.urlresolvers import reverse_lazy
4 | from django.views.generic import RedirectView
5 | from django.views.decorators.csrf import ensure_csrf_cookie
6 |
7 | # Uncomment the next two lines to enable the admin:
8 | # from django.contrib import admin
9 | # admin.autodiscover()
10 |
11 |
12 | urlpatterns = patterns('',
13 | # Examples:
14 |
15 | url(r'^$', RedirectView.as_view(url=reverse_lazy('dockerfile'))),
16 |
17 | url(r'^dockerfile/$', ensure_csrf_cookie(TemplateView.as_view(template_name='dockerfile/introduction.html')), name='dockerfile'),
18 | url(r'^dockerfile/level1/$', ensure_csrf_cookie(TemplateView.as_view(template_name='dockerfile/level1.html')), name='dockerfile_level1'),
19 | url(r'^dockerfile/level2/$', ensure_csrf_cookie(TemplateView.as_view(template_name='dockerfile/level2.html')), name='dockerfile_level2'),
20 |
21 | )
22 |
23 |
24 |
--------------------------------------------------------------------------------
/docker_tutorial/static/lib/js/jquery.mousewheel-min.js:
--------------------------------------------------------------------------------
1 | (function(c){function g(a){var b=a||window.event,i=[].slice.call(arguments,1),e=0,h=0,f=0;a=c.event.fix(b);a.type="mousewheel";if(b.wheelDelta)e=b.wheelDelta/120;if(b.detail)e=-b.detail/3;f=e;if(b.axis!==undefined&&b.axis===b.HORIZONTAL_AXIS){f=0;h=-1*e}if(b.wheelDeltaY!==undefined)f=b.wheelDeltaY/120;if(b.wheelDeltaX!==undefined)h=-1*b.wheelDeltaX/120;i.unshift(a,e,h,f);return(c.event.dispatch||c.event.handle).apply(this,i)}var d=["DOMMouseScroll","mousewheel"];if(c.event.fixHooks)for(var j=d.length;j;)c.event.fixHooks[d[--j]]=
2 | c.event.mouseHooks;c.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=d.length;a;)this.addEventListener(d[--a],g,false);else this.onmousewheel=g},teardown:function(){if(this.removeEventListener)for(var a=d.length;a;)this.removeEventListener(d[--a],g,false);else this.onmousewheel=null}};c.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery);
3 |
--------------------------------------------------------------------------------
/docker_tutorial/utils.py:
--------------------------------------------------------------------------------
1 | from django.core.cache import cache
2 | from django.core.exceptions import ObjectDoesNotExist
3 |
4 | from docker_tutorial.models import TutorialUser
5 |
6 |
7 | def get_user_for_request(request):
8 | """
9 | Function checks for an existing session, and creates one if necessary
10 | then checks for existing tutorialUser, and creates one if necessary
11 | returns this tutorialUser
12 | """
13 | session_key = request.session._get_or_create_session_key()
14 | return _get_or_create_user(request, session_key)
15 |
16 |
17 | def _get_or_create_user(request, session_key):
18 | """
19 | Fetches a user if one exists, creates one otherwise.
20 | """
21 | cache_prefix = "_get_or_create_user:"
22 | cache_key = cache_prefix + session_key
23 |
24 | user = cache.get(cache_key)
25 | if user is None:
26 | try:
27 | user = TutorialUser.objects.filter(
28 | session_key=session_key).latest('pk')
29 | except ObjectDoesNotExist:
30 | user = TutorialUser.objects.create(
31 | session_key=session_key,
32 |
33 | # store some metadata about the user
34 | http_user_agent=request.META.get('HTTP_USER_AGENT', ''),
35 | http_remote_address=request.META.get('REMOTE_ADDR', ''),
36 | http_real_remote_address=request.META.get(
37 | 'HTTP_X_FORWARDED_FOR', ''),
38 | http_accept_language=request.META.get(
39 | 'HTTP_ACCEPT_LANGUAGE', ''),
40 | http_referrer=request.META.get('HTTP_REFERER', '')
41 | )
42 | finally:
43 | cache.set(cache_key, user)
44 |
45 | return user
--------------------------------------------------------------------------------
/dockerfile_tutorial/templates/dockerfile/introduction.html:
--------------------------------------------------------------------------------
1 | {% extends 'dockerfile/_dockerfile.html' %}
2 | {% load staticfiles %}{% load google_analytics %}{% load navactive %}
3 |
4 | {% block content %}
5 |
6 |
7 |
8 |
9 |
10 |
Dockerfile tutorial
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Level 0 - Introduction
20 |
21 | As we showed in the introductory tutorial , containers can be built manually.
22 | However, with a Dockerfile, you can describe build steps once and then build a container automatically--any time you want--from source.
23 |
24 |
25 | Dockerfiles can be viewed as an image representation. They provide a simple syntax for building images and they are a great way to automate and script the images creation.
26 | If you are really serious about Docker, you should master the Dockerfile syntax. Let’s start!
27 |
28 |
29 | If you haven't taken the introductory tutorial , you should do so before starting this.
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | {% endblock %}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Docker Tutorial
2 | ===============
3 |
4 | The Docker tutorial is an interactive learning environment to get familiar with the Docker commandline.
5 |
6 |
7 | Simple Usage
8 | ------------
9 |
10 | Generally this application is used in the www.docker.io website. It's source can be found on
11 | https://github.com/dotcloud/www.docker.io/. By installing that you will get this app 'for free' as a
12 | dependency.
13 |
14 | However, this app is made to also be usable inside other Django applications. All that is required is to
15 | add git+https://github.com/dhrp/docker-tutorial.git#egg=DockerTutorial-dev to your requirements.txt
16 | and it will be installed by pip upon pip install -r requirements.txt
17 |
18 | To include it
19 | * Make sure your "host" app uses the same python environment
20 | * In your "host" app settings include "docker_tutorial"
21 | * in a template {% include 'tutorial/snippet.html' %}
22 | * in your urls.py add url(r'^tutorial/', include('docker_tutorial.urls')),
23 | * in your settings make sure you include the session middleware:
24 |
25 |
26 | When you want to make changes
27 | -----------------------------
28 |
29 | * First create or switch to a virtual environment in which you have the "host" app into which you would
30 | like to embed the tutorial. e.g. a clone of the the Docker website (before you ran install)
31 | * Clone this repository:
32 | git clone https://github.com/dhrp/docker-tutorial.git
33 | * Switch to the dir:
34 | cd docker-tutorial
35 | * Install the application with the -e (editable) flag.
36 | pip install -e .
37 |
38 | This will setup the symlinks such that you don't need to run setup.py every time you want to see a
39 | change. i.e. your local repository is now linked into the environment.
40 |
41 | Running the unit tests
42 | ----------------------
43 |
44 | * ./runtests.py
45 |
46 | Running code coverage
47 | ---------------------
48 |
49 | * coverage run ./runtests.py && coverage html --include="./docker_tutorial/*"
50 |
51 | Happy coding!
--------------------------------------------------------------------------------
/dockerfile_tutorial/static/js/dockerfile_tutorial.js:
--------------------------------------------------------------------------------
1 | if (!String.prototype.trim)
2 | {
3 | String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); };
4 | }
5 |
6 | function clean_input(i)
7 | {
8 | return (i.trim());
9 | }
10 |
11 | function dockerfile_log(level, item, errors)
12 | {
13 | var logUrl = '/tutorial/api/dockerfile_event/';
14 | $.ajax({
15 | url: logUrl,
16 | type: "POST",
17 | cache:false,
18 | data: {
19 | 'errors': errors,
20 | 'level': level,
21 | 'item': item,
22 | },
23 | }).done( function() { } );
24 | }
25 |
26 | function validate_email(email)
27 | {
28 | var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
29 | return re.test(email);
30 | }
31 |
32 | $(document).ready(function() {
33 |
34 | /* prepare to send the csrf-token on each ajax-request */
35 | var csrftoken = $.cookie('csrftoken');
36 | $.ajaxSetup({
37 | headers: { 'X-CSRFToken': csrftoken }
38 | });
39 |
40 | $("#send_email").click( function()
41 | {
42 | $('#email_invalid').hide();
43 | $('#email_already_registered').hide();
44 | $('#email_registered').hide();
45 |
46 | email = $('#email').val();
47 | if (!validate_email(email))
48 | {
49 | $('#email_invalid').show();
50 | return (false);
51 | }
52 |
53 | var emailUrl = '/tutorial/api/subscribe/';
54 |
55 | $.ajax({
56 | url: emailUrl,
57 | type: "POST",
58 | cache:false,
59 | data: {
60 | 'email': email,
61 | 'from_level': $(this).data('level')
62 | },
63 | }).done( function(data ) {
64 | if (data == 1) // already registered
65 | {
66 | $('#email_already_registered').show();
67 | }
68 | else if (data == 0) // registered ok
69 | {
70 | $('#email_registered').show();
71 | }
72 |
73 | } );
74 | return (true);
75 | });
76 | })
77 |
--------------------------------------------------------------------------------
/docker_tutorial/static/lib/css/jquery.terminal.css:
--------------------------------------------------------------------------------
1 | .terminal .terminal-output .format, .terminal .cmd .format,
2 | .terminal .cmd .prompt, .terminal .cmd .prompt div, .terminal .terminal-output div div{
3 | display: inline-block;
4 | }
5 | .terminal .clipboard {
6 | position: absolute;
7 | bottom: 0;
8 | left: 0;
9 | opacity: 0.01;
10 | filter: alpha(opacity = 0.01);
11 | filter: progid:DXImageTransform.Microsoft.Alpha(opacity=0.01);
12 | width: 2px;
13 | }
14 | .cmd > .clipboard {
15 | position: fixed;
16 | }
17 | .terminal {
18 | padding: 10px;
19 | position: relative;
20 | overflow: hidden;
21 | }
22 | .cmd {
23 | padding: 0;
24 | margin: 0;
25 | height: 1.3em;
26 | margin-top: 3px;
27 | }
28 | .terminal .terminal-output div div, .terminal .prompt {
29 | display: block;
30 | line-height: 14px;
31 | height: auto;
32 | }
33 | .terminal .prompt {
34 | float: left;
35 | }
36 |
37 | .terminal {
38 | font-family: FreeMono, monospace;
39 | color: #aaa;
40 | background-color: #000;
41 | font-size: 12px;
42 | line-height: 14px;
43 | }
44 | .terminal-output > div {
45 | padding-top: 3px;
46 | }
47 | .terminal .terminal-output div span {
48 | display: inline-block;
49 | }
50 | .terminal .cmd span {
51 | float: left;
52 | /*display: inline-block; */
53 | }
54 | .terminal .cmd span.inverted {
55 | background-color: #aaa;
56 | color: #000;
57 | }
58 | .terminal .terminal-output div div::-moz-selection,
59 | .terminal .terminal-output div span::-moz-selection,
60 | .terminal .terminal-output div div a::-moz-selection {
61 | background-color: #aaa;
62 | color: #000;
63 | }
64 | .terminal .terminal-output div div::selection,
65 | .terminal .terminal-output div div a::selection,
66 | .terminal .terminal-output div span::selection,
67 | .terminal .cmd > span::selection,
68 | .terminal .prompt span::selection {
69 | background-color: #aaa;
70 | color: #000;
71 | }
72 | .terminal .terminal-output div.error, .terminal .terminal-output div.error div {
73 | color: red;
74 | }
75 | .tilda {
76 | position: fixed;
77 | top: 0;
78 | left: 0;
79 | width: 100%;
80 | z-index: 1100;
81 | }
82 | .clear {
83 | clear: both;
84 | }
85 | .terminal a {
86 | color: #0F60FF;
87 | }
88 | .terminal a:hover {
89 | color: red;
90 | }
91 |
--------------------------------------------------------------------------------
/docker_tutorial/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.contrib.sessions.models import Session
3 | from .models import *
4 |
5 |
6 | class SessionAdmin(admin.ModelAdmin):
7 | def _session_data(self, obj):
8 | return obj.get_decoded()
9 | list_display = ['session_key', '_session_data', 'expire_date']
10 | admin.site.register(Session, SessionAdmin)
11 |
12 |
13 | class TutorialEventInline(admin.TabularInline):
14 | model = TutorialEvent
15 | readonly_fields = ('timestamp', 'time_elapsed', 'question', 'type', 'command',)
16 | fields = ['question', 'type', 'command', 'timestamp', 'time_elapsed']
17 | extra = 0
18 |
19 |
20 | class DockerfileEvenInline(admin.TabularInline):
21 | model = DockerfileEvent
22 | readonly_fields = ('timestamp', 'level', 'item', 'errors', )
23 | fields = ['level', 'item', 'errors', 'timestamp']
24 | extra = 0
25 |
26 |
27 | class TutorialUserAdmin(admin.ModelAdmin):
28 | model = TutorialUser
29 | list_display = ['id', 'session_key', 'label', 'timestamp']
30 | inlines = [TutorialEventInline, DockerfileEvenInline]
31 | admin.site.register(TutorialUser, TutorialUserAdmin)
32 |
33 |
34 | class TutorialEventAdmin(admin.ModelAdmin):
35 | model = TutorialEvent
36 | readonly_fields = ('id', 'timestamp', 'time_elapsed')
37 | fields = ['id', 'user', 'type', 'question', 'command', 'timestamp', 'time_elapsed']
38 | list_display = ['id', 'user','type', 'question', 'timestamp']
39 | admin.site.register(TutorialEvent, TutorialEventAdmin)
40 |
41 |
42 | class TutorialEventFeedback(TutorialEvent):
43 | class Meta:
44 | proxy = True
45 |
46 |
47 | class TutorialEventFeedbackAdmin(admin.ModelAdmin):
48 | model = TutorialEvent
49 | readonly_fields = ('id', 'timestamp', 'time_elapsed')
50 | list_display = ['id', 'user', 'question', 'feedback', 'timestamp']
51 | list_filter = ('type',)
52 | admin.site.register(TutorialEventFeedback, TutorialEventFeedbackAdmin)
53 |
54 |
55 | class SubscriberAdmin(admin.ModelAdmin):
56 | model = Subscriber
57 | list_display = ['email', 'from_level']
58 | admin.site.register(Subscriber, SubscriberAdmin)
59 |
60 |
61 | class DockerfileEventAdmin(admin.ModelAdmin):
62 | model = DockerfileEvent
63 | list_display = ['id', 'timestamp', 'item', 'level', 'errors']
64 | admin.site.register(DockerfileEvent, DockerfileEventAdmin)
--------------------------------------------------------------------------------
/dockerfile_tutorial/static/js/jquery.cookie.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery Cookie Plugin v1.3.1
3 | * https://github.com/carhartl/jquery-cookie
4 | *
5 | * Copyright 2013 Klaus Hartl
6 | * Released under the MIT license
7 | */
8 | (function ($, document, undefined) {
9 |
10 | var pluses = /\+/g;
11 |
12 | function raw(s) {
13 | return s;
14 | }
15 |
16 | function decoded(s) {
17 | return unRfc2068(decodeURIComponent(s.replace(pluses, ' ')));
18 | }
19 |
20 | function unRfc2068(value) {
21 | if (value.indexOf('"') === 0) {
22 | // This is a quoted cookie as according to RFC2068, unescape
23 | value = value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
24 | }
25 | return value;
26 | }
27 |
28 | function fromJSON(value) {
29 | return config.json ? JSON.parse(value) : value;
30 | }
31 |
32 | var config = $.cookie = function (key, value, options) {
33 |
34 | // write
35 | if (value !== undefined) {
36 | options = $.extend({}, config.defaults, options);
37 |
38 | if (value === null) {
39 | options.expires = -1;
40 | }
41 |
42 | if (typeof options.expires === 'number') {
43 | var days = options.expires, t = options.expires = new Date();
44 | t.setDate(t.getDate() + days);
45 | }
46 |
47 | value = config.json ? JSON.stringify(value) : String(value);
48 |
49 | return (document.cookie = [
50 | encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value),
51 | options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
52 | options.path ? '; path=' + options.path : '',
53 | options.domain ? '; domain=' + options.domain : '',
54 | options.secure ? '; secure' : ''
55 | ].join(''));
56 | }
57 |
58 | // read
59 | var decode = config.raw ? raw : decoded;
60 | var cookies = document.cookie.split('; ');
61 | var result = key ? null : {};
62 | for (var i = 0, l = cookies.length; i < l; i++) {
63 | var parts = cookies[i].split('=');
64 | var name = decode(parts.shift());
65 | var cookie = decode(parts.join('='));
66 |
67 | if (key && key === name) {
68 | result = fromJSON(cookie);
69 | break;
70 | }
71 |
72 | if (!key) {
73 | result[name] = fromJSON(cookie);
74 | }
75 | }
76 |
77 | return result;
78 | };
79 |
80 | config.defaults = {};
81 |
82 | $.removeCookie = function (key, options) {
83 | if ($.cookie(key) !== null) {
84 | $.cookie(key, null, options);
85 | return true;
86 | }
87 | return false;
88 | };
89 |
90 | })(jQuery, document);
91 |
--------------------------------------------------------------------------------
/docker_tutorial/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from datetime import datetime
3 |
4 | __author__ = 'thatcher'
5 |
6 |
7 | class TutorialUser(models.Model):
8 | session_key = models.CharField(max_length=40, unique=True) # natural key
9 | timestamp = models.DateTimeField(auto_now=True, default=datetime.now)
10 | label = models.CharField(max_length=80, default='', blank=True)
11 | http_user_agent = models.TextField(default='', blank=True)
12 | http_remote_address = models.TextField(default='', blank=True)
13 | http_real_remote_address = models.TextField(default='', blank=True)
14 | http_accept_language = models.TextField(default='', blank=True)
15 | http_referrer = models.TextField(default='', blank=True)
16 |
17 | def __unicode__(self):
18 | return u"%s" % (self.id)
19 |
20 |
21 | class TutorialEvent(models.Model):
22 |
23 | NONE = 'none'
24 | START = 'start'
25 | COMMAND = 'command'
26 | NEXT = 'next'
27 | FEEDBACK = 'feedback'
28 | PEEK = 'peek'
29 | COMPLETE = 'complete'
30 |
31 |
32 | EVENT_TYPES = (
33 | (NONE, u'No type set (debug)'),
34 | (START, u'Start tutorial'),
35 | (COMMAND, u'Command given'),
36 | (NEXT, u'Next Question'),
37 | (FEEDBACK, u'Feedback'),
38 | (PEEK, u'Peek'),
39 | (COMPLETE, u'Tutorial Complete')
40 | )
41 |
42 | user = models.ForeignKey(TutorialUser)
43 | type = models.CharField(max_length=15, choices=EVENT_TYPES, blank=False)
44 | timestamp = models.DateTimeField(auto_now=True)
45 | question = models.IntegerField(null=True, blank=True)
46 | command = models.CharField(max_length=80, blank=True, default="")
47 | feedback = models.CharField(max_length=2000, blank=True, default="")
48 |
49 | @property
50 | def time_elapsed(self):
51 | time_elapsed = self.timestamp - TutorialEvent.objects.filter(user=self.user).order_by('pk')[0].timestamp
52 | return time_elapsed.seconds
53 |
54 | def __unicode__(self):
55 | return str(self.id)
56 |
57 |
58 | class DockerfileEvent(models.Model):
59 |
60 | user = models.ForeignKey(TutorialUser)
61 | item = models.CharField(max_length=15, blank=False)
62 | timestamp = models.DateTimeField(auto_now=True)
63 | level = models.IntegerField(null=True, blank=True)
64 | errors = models.IntegerField(null=True, blank=True)
65 |
66 | @property
67 | def time_elapsed(self):
68 | time_elapsed = self.timestamp - TutorialEvent.objects.filter(user=self.user).order_by('pk')[0].timestamp
69 | return time_elapsed.seconds
70 |
71 | def __unicode__(self):
72 | return str(self.id)
73 |
74 |
75 | class Subscriber(models.Model):
76 |
77 | email = models.EmailField(max_length=80)
78 | user = models.ForeignKey(TutorialUser)
79 | timestamp = models.DateTimeField(auto_now=True)
80 | from_level = models.IntegerField()
81 |
82 | class Meta:
83 | unique_together = ("email", "from_level")
84 |
85 | def __unicode__(self):
86 | return u"{}".format(self.email)
87 |
--------------------------------------------------------------------------------
/docker_tutorial/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import datetime
3 | from south.db import db
4 | from south.v2 import SchemaMigration
5 | from django.db import models
6 |
7 |
8 | class Migration(SchemaMigration):
9 |
10 | def forwards(self, orm):
11 | # Adding model 'TutorialUser'
12 | db.create_table(u'docker_tutorial_tutorialuser', (
13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 | ('session_key', self.gf('django.db.models.fields.CharField')(max_length=80)),
15 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now, auto_now=True, blank=True)),
16 | ('label', self.gf('django.db.models.fields.CharField')(default='', max_length=80, null=True, blank=True)),
17 | ))
18 | db.send_create_signal(u'docker_tutorial', ['TutorialUser'])
19 |
20 | # Adding model 'TutorialEvent'
21 | db.create_table(u'docker_tutorial_tutorialevent', (
22 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
23 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['docker_tutorial.TutorialUser'])),
24 | ('type', self.gf('django.db.models.fields.CharField')(max_length=15)),
25 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
26 | ('question', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
27 | ('command', self.gf('django.db.models.fields.CharField')(default='', max_length=80, blank=True)),
28 | ('feedback', self.gf('django.db.models.fields.CharField')(default='', max_length=2000, blank=True)),
29 | ))
30 | db.send_create_signal(u'docker_tutorial', ['TutorialEvent'])
31 |
32 |
33 | def backwards(self, orm):
34 | # Deleting model 'TutorialUser'
35 | db.delete_table(u'docker_tutorial_tutorialuser')
36 |
37 | # Deleting model 'TutorialEvent'
38 | db.delete_table(u'docker_tutorial_tutorialevent')
39 |
40 |
41 | models = {
42 | u'docker_tutorial.tutorialevent': {
43 | 'Meta': {'object_name': 'TutorialEvent'},
44 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}),
45 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}),
46 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
47 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
48 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
49 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
50 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
51 | },
52 | u'docker_tutorial.tutorialuser': {
53 | 'Meta': {'object_name': 'TutorialUser'},
54 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
55 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'null': 'True', 'blank': 'True'}),
56 | 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
57 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'})
58 | }
59 | }
60 |
61 | complete_apps = ['docker_tutorial']
--------------------------------------------------------------------------------
/dockerfile_tutorial/static/js/dockerfile_tutorial_level1.js:
--------------------------------------------------------------------------------
1 | function check_form ()
2 | {
3 | $('#level1_error0').hide();
4 | $('#level1_error1').hide();
5 | $('#level1_error2').hide();
6 | $('#level1_error3').hide();
7 |
8 | $('#no_good').hide();
9 | $('#some_good').hide();
10 | $('#all_good').hide();
11 |
12 | var a = clean_input($('#level1_q0').val()).toUpperCase();
13 | var b = clean_input($('#level1_q1').val()).toUpperCase();
14 | var c = clean_input($('#level1_q2').val()).toUpperCase();
15 | var d = clean_input($('#level1_q3').val());
16 | var points = 0;
17 |
18 | if (a == 'FROM')
19 | {
20 | points = points + 1;
21 | }
22 | else
23 | {
24 | $('#level1_error0').show();
25 | }
26 | if (b == 'RUN')
27 | {
28 | points = points + 1;
29 | }
30 | else
31 | {
32 | $('#level1_error1').show();
33 | }
34 | if (c == 'MAINTAINER')
35 | {
36 | points = points + 1;
37 | }
38 | else
39 | {
40 | $('#level1_error2').show();
41 | }
42 | if (d == '#')
43 | {
44 | points = points + 1;
45 | }
46 | else
47 | {
48 | $('#level1_error3').show();
49 | }
50 | if (points == 4) // all good
51 | {
52 | $('#all_good').show();
53 | }
54 | else if (points == 0) // nothing good
55 | {
56 | $('#no_good').show();
57 | }
58 | else // some good some bad
59 | {
60 | $('#some_good').show();
61 | }
62 | return (4 - points);
63 | }
64 |
65 |
66 |
67 | function check_fill()
68 | {
69 |
70 | $('#dockerfile_ok').hide();
71 | $('#dockerfile_ko').hide();
72 |
73 | var from = clean_input($('#from').val()).toUpperCase();
74 | var ubuntu = clean_input($('#ubuntu').val()).toUpperCase();
75 | var maintainer = clean_input($('#maintainer').val()).toUpperCase();
76 | var eric = clean_input($('#eric').val()).toUpperCase();
77 | var bardin = clean_input($('#bardin').val()).toUpperCase();
78 | var run0 = clean_input($('#run0').val()).toUpperCase();
79 | var run1 = clean_input($('#run1').val()).toUpperCase();
80 | var memcached = clean_input($('#memcached').val()).toUpperCase();
81 | var run2 = clean_input($('#run2').val()).toUpperCase();
82 |
83 | $('#from').attr("class", "input-small");
84 | $('#ubuntu').attr("class", "input-small");
85 | $('#maintainer').attr("class", "input-small");
86 | $('#eric').attr("class", "input-small");
87 | $('#bardin').attr("class", "input-small");
88 | $('#run0').attr("class", "input-small");
89 | $('#run1').attr("class", "input-small");
90 | $('#memcached').attr("class", "input-small");
91 | $('#run2').attr("class", "input-small");
92 |
93 | var errors = 0;
94 |
95 | if (from != "FROM")
96 | {
97 | $('#from').attr("class", "input-small error_input");
98 | errors = errors + 1;
99 | }
100 | if (ubuntu != "UNTU")
101 | {
102 | $('#ubuntu').attr("class", "input-small error_input");
103 | errors = errors + 1;
104 | }
105 | if (maintainer != "MAINTAINER")
106 | {
107 | $('#maintainer').attr("class", "input-small error_input");
108 | errors = errors + 1;
109 | }
110 | if (eric != "RIC")
111 | {
112 | $('#eric').attr("class", "input-small error_input");
113 | errors = errors + 1;
114 | }
115 | if (bardin != "ARDIN")
116 | {
117 | $('#bardin').attr("class", "input-small error_input");
118 | errors = errors + 1;
119 | }
120 | if (run0 != "RUN")
121 | {
122 | $('#run0').attr("class", "input-small error_input");
123 | errors = errors + 1;
124 | }
125 | if (run1 != "RUN")
126 | {
127 | $('#run1').attr("class", "input-small error_input");
128 | errors = errors + 1;
129 | }
130 | if (run2 != "RUN")
131 | {
132 | $('#run2').attr("class", "input-small error_input");
133 | errors = errors + 1;
134 | }
135 | if (memcached != "MEMCACHED")
136 | {
137 | $('#memcached').attr("class", "input-small error_input");
138 | errors = errors + 1;
139 | }
140 |
141 | if (errors != 0)
142 | {
143 | $('#dockerfile_ko').show();
144 | }
145 | else
146 | {
147 | $('#dockerfile_ok').show();
148 | }
149 | return (errors);
150 | }
151 |
152 | $(document).ready(function() {
153 |
154 | $("#check_level1_questions").click( function()
155 | {
156 | errors = check_form();
157 | dockerfile_log(1, '1_questions', errors);
158 | }
159 | );
160 |
161 | $("#check_level1_fill").click( function()
162 | {
163 | var errors = check_fill();
164 | dockerfile_log(1, '2_fill', errors);
165 | });
166 | });
167 |
--------------------------------------------------------------------------------
/docker_tutorial/migrations/0004_auto__del_field_tutorialuser_http_accept_encoding__add_field_tutorialu.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import datetime
3 | from south.db import db
4 | from south.v2 import SchemaMigration
5 | from django.db import models
6 |
7 |
8 | class Migration(SchemaMigration):
9 |
10 | def forwards(self, orm):
11 | # Deleting field 'TutorialUser.http_accept_encoding'
12 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_accept_encoding')
13 |
14 | # Adding field 'TutorialUser.http_real_remote_address'
15 | db.add_column(u'docker_tutorial_tutorialuser', 'http_real_remote_address',
16 | self.gf('django.db.models.fields.CharField')(default='', max_length=32, blank=True),
17 | keep_default=False)
18 |
19 |
20 | def backwards(self, orm):
21 | # Adding field 'TutorialUser.http_accept_encoding'
22 | db.add_column(u'docker_tutorial_tutorialuser', 'http_accept_encoding',
23 | self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True),
24 | keep_default=False)
25 |
26 | # Deleting field 'TutorialUser.http_real_remote_address'
27 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_real_remote_address')
28 |
29 |
30 | models = {
31 | u'docker_tutorial.dockerfileevent': {
32 | 'Meta': {'object_name': 'DockerfileEvent'},
33 | 'errors': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
34 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
35 | 'item': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
36 | 'level': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
37 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
38 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
39 | },
40 | u'docker_tutorial.subscriber': {
41 | 'Meta': {'unique_together': "(('email', 'from_level'),)", 'object_name': 'Subscriber'},
42 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '80'}),
43 | 'from_level': ('django.db.models.fields.IntegerField', [], {}),
44 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
45 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
46 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
47 | },
48 | u'docker_tutorial.tutorialevent': {
49 | 'Meta': {'object_name': 'TutorialEvent'},
50 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}),
51 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}),
52 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
53 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
54 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
55 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
56 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
57 | },
58 | u'docker_tutorial.tutorialuser': {
59 | 'Meta': {'object_name': 'TutorialUser'},
60 | 'http_accept_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
61 | 'http_real_remote_address': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
62 | 'http_referrer': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
63 | 'http_remote_address': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
64 | 'http_user_agent': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
65 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
66 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}),
67 | 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
68 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'})
69 | }
70 | }
71 |
72 | complete_apps = ['docker_tutorial']
--------------------------------------------------------------------------------
/docker_tutorial/migrations/0002_auto__add_subscriber__add_unique_subscriber_email_from_level__add_dock.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import datetime
3 | from south.db import db
4 | from south.v2 import SchemaMigration
5 | from django.db import models
6 |
7 |
8 | class Migration(SchemaMigration):
9 |
10 | def forwards(self, orm):
11 | # Adding model 'Subscriber'
12 | db.create_table(u'docker_tutorial_subscriber', (
13 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
14 | ('email', self.gf('django.db.models.fields.EmailField')(max_length=80)),
15 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['docker_tutorial.TutorialUser'])),
16 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
17 | ('from_level', self.gf('django.db.models.fields.IntegerField')()),
18 | ))
19 | db.send_create_signal(u'docker_tutorial', ['Subscriber'])
20 |
21 | # Adding unique constraint on 'Subscriber', fields ['email', 'from_level']
22 | db.create_unique(u'docker_tutorial_subscriber', ['email', 'from_level'])
23 |
24 | # Adding model 'DockerfileEvent'
25 | db.create_table(u'docker_tutorial_dockerfileevent', (
26 | (u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
27 | ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['docker_tutorial.TutorialUser'])),
28 | ('item', self.gf('django.db.models.fields.CharField')(max_length=15)),
29 | ('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
30 | ('level', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
31 | ('errors', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
32 | ))
33 | db.send_create_signal(u'docker_tutorial', ['DockerfileEvent'])
34 |
35 |
36 | def backwards(self, orm):
37 | # Removing unique constraint on 'Subscriber', fields ['email', 'from_level']
38 | db.delete_unique(u'docker_tutorial_subscriber', ['email', 'from_level'])
39 |
40 | # Deleting model 'Subscriber'
41 | db.delete_table(u'docker_tutorial_subscriber')
42 |
43 | # Deleting model 'DockerfileEvent'
44 | db.delete_table(u'docker_tutorial_dockerfileevent')
45 |
46 |
47 | models = {
48 | u'docker_tutorial.dockerfileevent': {
49 | 'Meta': {'object_name': 'DockerfileEvent'},
50 | 'errors': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
51 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
52 | 'item': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
53 | 'level': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
54 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
55 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
56 | },
57 | u'docker_tutorial.subscriber': {
58 | 'Meta': {'unique_together': "(('email', 'from_level'),)", 'object_name': 'Subscriber'},
59 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '80'}),
60 | 'from_level': ('django.db.models.fields.IntegerField', [], {}),
61 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
62 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
63 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
64 | },
65 | u'docker_tutorial.tutorialevent': {
66 | 'Meta': {'object_name': 'TutorialEvent'},
67 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}),
68 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}),
69 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
70 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
71 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
72 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
73 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
74 | },
75 | u'docker_tutorial.tutorialuser': {
76 | 'Meta': {'object_name': 'TutorialUser'},
77 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
78 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'null': 'True', 'blank': 'True'}),
79 | 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
80 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'})
81 | }
82 | }
83 |
84 | complete_apps = ['docker_tutorial']
--------------------------------------------------------------------------------
/docker_tutorial/views.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | from django.shortcuts import render_to_response
4 | from django.template import RequestContext
5 | from django.views.decorators.csrf import csrf_exempt
6 | from django.http import HttpResponse
7 |
8 | from . import utils
9 | from .models import TutorialUser, TutorialEvent, DockerfileEvent, Subscriber
10 |
11 | __author__ = 'thatcher'
12 |
13 |
14 | def endpoint(request):
15 | """
16 | api endpoint for saving events
17 | """
18 |
19 | return render_to_response("base/home.html", {
20 | }, context_instance=RequestContext(request))
21 |
22 |
23 | def testpage(request):
24 | """
25 | testing saving stuff on a users' session
26 | """
27 |
28 | username = request.session.get('username', 'unknown')
29 | print username
30 |
31 | return render_to_response("tutorial/testpage.html", {
32 | # "form": form,
33 | 'username': username
34 | }, context_instance=RequestContext(request))
35 |
36 |
37 | @csrf_exempt
38 | def api(request):
39 | """
40 | saving the user's events
41 | """
42 | if request.method == "POST":
43 | user = utils.get_user_for_request(request)
44 |
45 | event = TutorialEvent.objects.create(
46 | user=user,
47 | type=request.POST.get('type', TutorialEvent.NONE),
48 | question=request.POST.get('question', None),
49 | command=request.POST.get('command', "")[:79],
50 | feedback=request.POST.get('feedback', ""),
51 | )
52 |
53 | return HttpResponse('.')
54 |
55 |
56 | def dockerfile_event(request):
57 | """
58 | saving the dockerfile events
59 | """
60 |
61 | if request.method == "POST":
62 | user = utils.get_user_for_request(request)
63 | event = DockerfileEvent.objects.create(user=user)
64 | # event.user ; is set at instantiation
65 | ## default type is 'none'
66 | event.errors = request.POST.get('errors', None)
67 | event.level = request.POST.get('level', None)
68 | event.item = request.POST.get('item', 'none')
69 | # event.timestamp ; is automatically set
70 | event.save()
71 |
72 | return HttpResponse('0')
73 |
74 |
75 | def subscribe(request):
76 | """
77 | Save the users' email. -- Mainly / only meant for notifying them of the availability of
78 | a next tutorial
79 | """
80 | if request.POST:
81 | user = utils.get_user_for_request(request)
82 | email = request.POST.get('email', None)
83 | from_level = request.POST.get('from_level', None)
84 |
85 | try:
86 | subscriber = Subscriber.objects.create(
87 | user=user, email=email, from_level=from_level)
88 | Subscriber.save(subscriber)
89 | return HttpResponse('0', status=200)
90 | except:
91 | return HttpResponse('1', status=200)
92 | else:
93 | return HttpResponse('1', status=200)
94 |
95 |
96 | def stats(request):
97 |
98 |
99 | users = {}
100 | users['started'] = TutorialUser.objects.all().count()
101 | users['completed'] = TutorialEvent.objects.values_list('user', 'type').filter(type='complete').distinct().count()
102 |
103 | i = 0
104 | answered = {}
105 | while i < 9:
106 | number = TutorialEvent.objects.values_list('user', 'type', 'question').filter(type='next', question=i).distinct().count()
107 | answered[i] = number
108 | i = i + 1
109 |
110 | # Append the completed, because question 9 has no 'next'
111 | answered[9] = users['completed']
112 |
113 |
114 | # find which question people look for the answer
115 | i = 0
116 | peeks = {}
117 | while i < 10:
118 | number = TutorialEvent.objects.values_list('user', 'type', 'question').filter(type='peek', question=i).distinct().count()
119 | peeks[i] = number
120 | i = i + 1
121 |
122 | outputformat = request.GET.get("format", None)
123 | if outputformat:
124 | response = {}
125 | response['users'] = users
126 | response['peeks'] = peeks
127 | response['answered'] = answered
128 | jsonresponse = json.dumps(response)
129 | return HttpResponse(jsonresponse, mimetype="application/json")
130 |
131 | return render_to_response("tutorial/stats.html", {
132 | 'users': users,
133 | 'peeks': peeks,
134 | 'answered': answered
135 | }, context_instance=RequestContext(request))
136 |
137 |
138 | def get_metrics(request):
139 |
140 | users = {}
141 |
142 | # TutorialUsers = TutorialUser.objects.all()
143 | # users['started_interactive'] = TutorialUsers.filter(TutorialEvent__count > 0)
144 | # users['completed'] = TutorialEvent.objects.filter(type='complete').count()
145 |
146 | interactive_start_count = TutorialEvent.objects.values_list('user').distinct().count()
147 | interactive_complete_count = TutorialEvent.objects.values_list('user','type').filter(type=TutorialEvent.COMPLETE).distinct().count()
148 |
149 | dockerfile_tutorial_count = DockerfileEvent.objects.values_list('user').distinct().count()
150 | dockerfile_tutorial_complete_level1 = DockerfileEvent.objects.values_list('user', 'level', 'errors').filter(errors=0).distinct().count()
151 |
152 | response = []
153 | response.append({'name': 'tutorial_interactive_started', 'value_i': interactive_start_count})
154 | response.append({'name': 'tutorial_interactive_completed', 'value_i': interactive_complete_count})
155 | response.append({'name': 'dockerfile_tutorial_total', 'value_i': dockerfile_tutorial_count})
156 | response.append({'name': 'tutorial_dockerfile_completed_level1', 'value_i': dockerfile_tutorial_complete_level1})
157 |
158 | jsonresponse = json.dumps(response)
159 |
160 | return HttpResponse(jsonresponse, mimetype="application/json")
161 |
--------------------------------------------------------------------------------
/docker_tutorial/templates/tutorial/snippet.html:
--------------------------------------------------------------------------------
1 | {% load staticfiles %}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
Learn Docker
13 |
14 |
15 |
16 |
17 |
18 |
19 | >
21 |
22 | i
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
33 |
34 | 1
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
49 |
50 | i
51 |
52 |
53 |
54 |
55 |
Tips
56 | {#
#}
57 | {# click here for tips#}
58 | {#
#}
59 |
60 | This is what the written tip looks like
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
Show expected command
69 |
70 | click here to see the expected command
71 |
72 |
73 | This is what the written tip looks like
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | nothing here
82 |
83 |
84 |
85 |
86 |
Learn the first steps of using Docker, such as:
87 |
88 | Finding and downloading images
89 | Running hello world
90 | Committing your changes
91 | Pushing an image to the repository
92 |
93 |
94 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
106 |
107 | ✔
108 |
109 |
110 |
111 |
112 |
113 |
next
114 |
Finish and see what's next!
115 |
116 |
117 |
118 |
119 | Submit
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/docker_tutorial/migrations/0005_auto__chg_field_tutorialuser_http_user_agent__chg_field_tutorialuser_h.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import datetime
3 | from south.db import db
4 | from south.v2 import SchemaMigration
5 | from django.db import models
6 |
7 |
8 | class Migration(SchemaMigration):
9 |
10 | def forwards(self, orm):
11 |
12 | # Changing field 'TutorialUser.http_user_agent'
13 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_user_agent', self.gf('django.db.models.fields.TextField')())
14 |
15 | # Changing field 'TutorialUser.http_real_remote_address'
16 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_real_remote_address', self.gf('django.db.models.fields.TextField')())
17 |
18 | # Changing field 'TutorialUser.http_remote_address'
19 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_remote_address', self.gf('django.db.models.fields.TextField')())
20 |
21 | # Changing field 'TutorialUser.http_accept_language'
22 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_accept_language', self.gf('django.db.models.fields.TextField')())
23 |
24 | # Changing field 'TutorialUser.http_referrer'
25 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_referrer', self.gf('django.db.models.fields.TextField')())
26 |
27 | # Changing field 'TutorialUser.session_key'
28 | db.alter_column(u'docker_tutorial_tutorialuser', 'session_key', self.gf('django.db.models.fields.CharField')(unique=True, max_length=40))
29 | # Adding unique constraint on 'TutorialUser', fields ['session_key']
30 | db.create_unique(u'docker_tutorial_tutorialuser', ['session_key'])
31 |
32 |
33 | def backwards(self, orm):
34 | # Removing unique constraint on 'TutorialUser', fields ['session_key']
35 | db.delete_unique(u'docker_tutorial_tutorialuser', ['session_key'])
36 |
37 |
38 | # Changing field 'TutorialUser.http_user_agent'
39 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_user_agent', self.gf('django.db.models.fields.CharField')(max_length=256))
40 |
41 | # Changing field 'TutorialUser.http_real_remote_address'
42 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_real_remote_address', self.gf('django.db.models.fields.CharField')(max_length=32))
43 |
44 | # Changing field 'TutorialUser.http_remote_address'
45 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_remote_address', self.gf('django.db.models.fields.CharField')(max_length=32))
46 |
47 | # Changing field 'TutorialUser.http_accept_language'
48 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_accept_language', self.gf('django.db.models.fields.CharField')(max_length=128))
49 |
50 | # Changing field 'TutorialUser.http_referrer'
51 | db.alter_column(u'docker_tutorial_tutorialuser', 'http_referrer', self.gf('django.db.models.fields.CharField')(max_length=128))
52 |
53 | # Changing field 'TutorialUser.session_key'
54 | db.alter_column(u'docker_tutorial_tutorialuser', 'session_key', self.gf('django.db.models.fields.CharField')(max_length=80))
55 |
56 | models = {
57 | u'docker_tutorial.dockerfileevent': {
58 | 'Meta': {'object_name': 'DockerfileEvent'},
59 | 'errors': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
60 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
61 | 'item': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
62 | 'level': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
63 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
64 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
65 | },
66 | u'docker_tutorial.subscriber': {
67 | 'Meta': {'unique_together': "(('email', 'from_level'),)", 'object_name': 'Subscriber'},
68 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '80'}),
69 | 'from_level': ('django.db.models.fields.IntegerField', [], {}),
70 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
71 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
72 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
73 | },
74 | u'docker_tutorial.tutorialevent': {
75 | 'Meta': {'object_name': 'TutorialEvent'},
76 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}),
77 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}),
78 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
79 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
80 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
81 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
82 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
83 | },
84 | u'docker_tutorial.tutorialuser': {
85 | 'Meta': {'object_name': 'TutorialUser'},
86 | 'http_accept_language': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
87 | 'http_real_remote_address': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
88 | 'http_referrer': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
89 | 'http_remote_address': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
90 | 'http_user_agent': ('django.db.models.fields.TextField', [], {'default': "''", 'blank': 'True'}),
91 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
92 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}),
93 | 'session_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
94 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'})
95 | }
96 | }
97 |
98 | complete_apps = ['docker_tutorial']
--------------------------------------------------------------------------------
/docker_tutorial/migrations/0003_auto__add_field_tutorialuser_http_user_agent__add_field_tutorialuser_h.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import datetime
3 | from south.db import db
4 | from south.v2 import SchemaMigration
5 | from django.db import models
6 |
7 |
8 | class Migration(SchemaMigration):
9 |
10 | def forwards(self, orm):
11 | # Adding field 'TutorialUser.http_user_agent'
12 | db.add_column(u'docker_tutorial_tutorialuser', 'http_user_agent',
13 | self.gf('django.db.models.fields.CharField')(default='', max_length=256, blank=True),
14 | keep_default=False)
15 |
16 | # Adding field 'TutorialUser.http_remote_address'
17 | db.add_column(u'docker_tutorial_tutorialuser', 'http_remote_address',
18 | self.gf('django.db.models.fields.CharField')(default='', max_length=32, blank=True),
19 | keep_default=False)
20 |
21 | # Adding field 'TutorialUser.http_accept_language'
22 | db.add_column(u'docker_tutorial_tutorialuser', 'http_accept_language',
23 | self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True),
24 | keep_default=False)
25 |
26 | # Adding field 'TutorialUser.http_accept_encoding'
27 | db.add_column(u'docker_tutorial_tutorialuser', 'http_accept_encoding',
28 | self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True),
29 | keep_default=False)
30 |
31 | # Adding field 'TutorialUser.http_referrer'
32 | db.add_column(u'docker_tutorial_tutorialuser', 'http_referrer',
33 | self.gf('django.db.models.fields.CharField')(default='', max_length=128, blank=True),
34 | keep_default=False)
35 |
36 |
37 | # Changing field 'TutorialUser.label'
38 | db.alter_column(u'docker_tutorial_tutorialuser', 'label', self.gf('django.db.models.fields.CharField')(max_length=80))
39 |
40 | def backwards(self, orm):
41 | # Deleting field 'TutorialUser.http_user_agent'
42 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_user_agent')
43 |
44 | # Deleting field 'TutorialUser.http_remote_address'
45 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_remote_address')
46 |
47 | # Deleting field 'TutorialUser.http_accept_language'
48 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_accept_language')
49 |
50 | # Deleting field 'TutorialUser.http_accept_encoding'
51 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_accept_encoding')
52 |
53 | # Deleting field 'TutorialUser.http_referrer'
54 | db.delete_column(u'docker_tutorial_tutorialuser', 'http_referrer')
55 |
56 |
57 | # Changing field 'TutorialUser.label'
58 | db.alter_column(u'docker_tutorial_tutorialuser', 'label', self.gf('django.db.models.fields.CharField')(max_length=80, null=True))
59 |
60 | models = {
61 | u'docker_tutorial.dockerfileevent': {
62 | 'Meta': {'object_name': 'DockerfileEvent'},
63 | 'errors': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
64 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
65 | 'item': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
66 | 'level': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
67 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
68 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
69 | },
70 | u'docker_tutorial.subscriber': {
71 | 'Meta': {'unique_together': "(('email', 'from_level'),)", 'object_name': 'Subscriber'},
72 | 'email': ('django.db.models.fields.EmailField', [], {'max_length': '80'}),
73 | 'from_level': ('django.db.models.fields.IntegerField', [], {}),
74 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
76 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
77 | },
78 | u'docker_tutorial.tutorialevent': {
79 | 'Meta': {'object_name': 'TutorialEvent'},
80 | 'command': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}),
81 | 'feedback': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '2000', 'blank': 'True'}),
82 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
83 | 'question': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
84 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
85 | 'type': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
86 | 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['docker_tutorial.TutorialUser']"})
87 | },
88 | u'docker_tutorial.tutorialuser': {
89 | 'Meta': {'object_name': 'TutorialUser'},
90 | 'http_accept_encoding': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
91 | 'http_accept_language': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
92 | 'http_referrer': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '128', 'blank': 'True'}),
93 | 'http_remote_address': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '32', 'blank': 'True'}),
94 | 'http_user_agent': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '256', 'blank': 'True'}),
95 | u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
96 | 'label': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '80', 'blank': 'True'}),
97 | 'session_key': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
98 | 'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'auto_now': 'True', 'blank': 'True'})
99 | }
100 | }
101 |
102 | complete_apps = ['docker_tutorial']
--------------------------------------------------------------------------------
/dockerfile_tutorial/static/js/dockerfile_tutorial_level2.js:
--------------------------------------------------------------------------------
1 | function check_form ()
2 | {
3 | $('#level2_error0').hide();
4 | $('#level2_error1').hide();
5 | $('#level2_error2').hide();
6 | $('#level2_error3').hide();
7 | $('#level2_error4').hide();
8 | $('#level2_error5').hide();
9 | $('#level2_error6').hide();
10 | $('#level2_error7').hide();
11 |
12 | $('#no_good').hide();
13 | $('#some_good').hide();
14 | $('#all_good').hide();
15 |
16 | var a = clean_input($('#level2_q0').val()).toUpperCase();
17 | var b = clean_input($('#level2_q1').val()).toUpperCase();
18 | var c = clean_input($('#level2_q2').val()).toUpperCase();
19 | var d = clean_input($('#level2_q3').val());
20 | var e = clean_input($('#level2_q4').val()).toUpperCase();
21 | var f = clean_input($('#level2_q5').val()).toUpperCase();
22 | var g = clean_input($('#level2_q6').val()).toUpperCase();
23 | var h = clean_input($('#level2_q7').val()).toUpperCase();
24 | var points = 0;
25 |
26 | if (a == 'FROM')
27 | {
28 | points = points + 1;
29 | }
30 | else
31 | {
32 | $('#level2_error0').show();
33 | }
34 | if (b == 'RUN')
35 | {
36 | points = points + 1;
37 | }
38 | else
39 | {
40 | $('#level2_error1').show();
41 | }
42 | if (c == 'MAINTAINER')
43 | {
44 | points = points + 1;
45 | }
46 | else
47 | {
48 | $('#level2_error2').show();
49 | }
50 | if (d == '#')
51 | {
52 | points = points + 1;
53 | }
54 | else
55 | {
56 | $('#level2_error3').show();
57 | }
58 |
59 | if (e == 'ENTRYPOINT' || e == 'CMD')
60 | {
61 | points = points + 1;
62 | }
63 | else
64 | {
65 | $('#level2_error4').show();
66 | }
67 |
68 | if (f == 'USER')
69 | {
70 | points = points + 1;
71 | }
72 | else
73 | {
74 | $('#level2_error5').show();
75 | }
76 |
77 | if (g == 'EXPOSE')
78 | {
79 | points = points + 1;
80 | }
81 | else
82 | {
83 | $('#level2_error6').show();
84 | }
85 | if (h == 'ENTRYPOINT' || h == 'CMD')
86 | {
87 | points = points + 1;
88 | }
89 | else
90 | {
91 | $('#level2_error7').show();
92 | }
93 |
94 |
95 | if (points == 8) // all good
96 | {
97 | $('#all_good').show();
98 | }
99 | else if (points == 0) // nothing good
100 | {
101 | $('#no_good').show();
102 | }
103 | else // some good some bad
104 | {
105 | $('#some_good').show();
106 | }
107 | return (8- points);
108 | }
109 |
110 | function check_fill()
111 | {
112 | $('#dockerfile_ok').hide();
113 | $('#dockerfile_ko').hide();
114 |
115 | var from = clean_input($('#from').val()).toUpperCase();
116 | var ubuntu = clean_input($('#ubuntu').val()).toUpperCase();
117 | var maintainer = clean_input($('#maintainer').val()).toUpperCase();
118 | var eric = clean_input($('#eric').val()).toUpperCase();
119 | var bardin = clean_input($('#bardin').val()).toUpperCase();
120 | var run0 = clean_input($('#run0').val()).toUpperCase();
121 | var run1 = clean_input($('#run1').val()).toUpperCase();
122 | var run2 = clean_input($('#run2').val()).toUpperCase();
123 | var run3 = clean_input($('#run3').val()).toUpperCase();
124 | var run4 = clean_input($('#run4').val()).toUpperCase();
125 | var run5 = clean_input($('#run5').val()).toUpperCase();
126 | var run6 = clean_input($('#run6').val()).toUpperCase();
127 | var entrypoint = clean_input($('#entrypoint').val()).toUpperCase();
128 | var user = clean_input($('#user').val()).toUpperCase();
129 | var expose = clean_input($('#expose').val()).toUpperCase();
130 | var gcc = clean_input($('#gcc').val()).toUpperCase();
131 |
132 | $('#from').attr("class", "input-small");
133 | $('#ubuntu').attr("class", "input-small");
134 | $('#maintainer').attr("class", "input-small");
135 | $('#eric').attr("class", "input-small");
136 | $('#bardin').attr("class", "input-small");
137 | $('#run0').attr("class", "input-small");
138 | $('#run1').attr("class", "input-small");
139 | $('#run2').attr("class", "input-small");
140 | $('#run3').attr("class", "input-small");
141 | $('#run4').attr("class", "input-small");
142 | $('#run5').attr("class", "input-small");
143 | $('#run6').attr("class", "input-small");
144 |
145 | $('#entrypoint').attr("class", "input-small");
146 | $('#user').attr("class", "input-small");
147 | $('#expose').attr("class", "input-small");
148 | $('#gcc').attr("class", "input-small");
149 |
150 | var errors = 0;
151 |
152 | if (from != "FROM")
153 | {
154 | $('#from').attr("class", "input-small error_input");
155 | errors = errors + 1;
156 | }
157 | if (ubuntu != "UNTU")
158 | {
159 | $('#ubuntu').attr("class", "input-small error_input");
160 | errors = errors + 1;
161 | }
162 | if (maintainer != "AINER")
163 | {
164 | $('#maintainer').attr("class", "input-small error_input");
165 | errors = errors + 1;
166 | }
167 | if (eric != "BERTO")
168 | {
169 | $('#eric').attr("class", "input-small error_input");
170 | errors = errors + 1;
171 | }
172 | if (bardin != "SHIOKA")
173 | {
174 | $('#bardin').attr("class", "input-small error_input");
175 | errors = errors + 1;
176 | }
177 | if (run0 != "RUN")
178 | {
179 | $('#run0').attr("class", "input-small error_input");
180 | errors = errors + 1;
181 | }
182 | if (run1 != "RUN")
183 | {
184 | $('#run1').attr("class", "input-small error_input");
185 | errors = errors + 1;
186 | }
187 | if (run2 != "RUN")
188 | {
189 | $('#run2').attr("class", "input-small error_input");
190 | errors = errors + 1;
191 | }
192 | if (run3 != "RUN")
193 | {
194 | $('#run3').attr("class", "input-small error_input");
195 | errors = errors + 1;
196 | }
197 | if (run4 != "RUN")
198 | {
199 | $('#run4').attr("class", "input-small error_input");
200 | errors = errors + 1;
201 | }
202 | if (run5 != "RUN")
203 | {
204 | $('#run5').attr("class", "input-small error_input");
205 | errors = errors + 1;
206 | }
207 | if (run6 != "RUN")
208 | {
209 | $('#run6').attr("class", "input-small error_input");
210 | errors = errors + 1;
211 | }
212 |
213 | if (gcc != "GCC")
214 | {
215 | $('#gcc').attr("class", "input-small error_input");
216 | errors = errors + 1;
217 | }
218 | if (entrypoint != "ENTRYPOINT")
219 | {
220 | $('#entrypoint').attr("class", "input-small error_input");
221 | errors = errors + 1;
222 | }
223 | if (user != "USER")
224 | {
225 | $('#user').attr("class", "input-small error_input");
226 | errors = errors + 1;
227 | }
228 | if (expose != "EXPOSE")
229 | {
230 | $('#expose').attr("class", "input-small error_input");
231 | errors = errors + 1;
232 | }
233 |
234 | if (errors != 0)
235 | {
236 | $('#dockerfile_ko').show();
237 | }
238 | else
239 | {
240 | $('#dockerfile_ok').show();
241 | }
242 | return (errors);
243 | }
244 |
245 | $(document).ready(function() {
246 |
247 | $("#check_level2_questions").click( function()
248 | {
249 | errors = check_form();
250 | dockerfile_log(2, '1_questions', errors);
251 | }
252 | );
253 |
254 | $("#check_level2_fill").click( function()
255 | {
256 | var errors = check_fill();
257 | dockerfile_log(2, '2_fill', errors);
258 | });
259 | });
260 |
--------------------------------------------------------------------------------
/docker_tutorial/static/css/tutorial-style.css:
--------------------------------------------------------------------------------
1 | .debug {
2 | border: 2px red dotted;
3 | background: #361919;
4 | /* Fall-back for browsers that don't support rgba */
5 |
6 | background: rgba(54, 25, 25, 0.5);
7 | box-sizing: border-box;
8 | }
9 | /*
10 | Styles largely duplicates from the docker website
11 | */
12 | .tutorial {
13 | font-family: "Cabin", "Helvetica Neue", Helvetica, Arial, sans-serif;
14 | font-size: 14px;
15 | line-height: 20px;
16 | color: #333333;
17 | }
18 | .tutorial h1,
19 | .tutorial h2,
20 | .tutorial h3,
21 | .tutorial h4,
22 | .tutorial h5,
23 | .tutorial h6 {
24 | margin: 10px 0;
25 | font-family: inherit;
26 | font-weight: normal;
27 | line-height: 20px;
28 | color: inherit;
29 | text-rendering: optimizelegibility;
30 | }
31 | .tutorial h1,
32 | .tutorial h2,
33 | .tutorial h3 {
34 | line-height: 40px;
35 | }
36 | .tutorial img {
37 | max-width: 100%;
38 | width: auto\9;
39 | height: auto;
40 | vertical-align: middle;
41 | border: 0;
42 | -ms-interpolation-mode: bicubic;
43 | }
44 | .tutorial select,
45 | .tutorial textarea,
46 | .tutorial input[type="text"],
47 | .tutorial input[type="password"],
48 | .tutorial input[type="datetime"],
49 | .tutorial input[type="datetime-local"],
50 | .tutorial input[type="date"],
51 | .tutorial input[type="month"],
52 | .tutorial input[type="time"],
53 | .tutorial input[type="week"],
54 | .tutorial input[type="number"],
55 | .tutorial input[type="email"],
56 | .tutorial input[type="url"],
57 | .tutorial input[type="search"],
58 | .tutorial input[type="tel"],
59 | .tutorial input[type="color"],
60 | .tutorial .uneditable-input {
61 | display: inline-block;
62 | height: 20px;
63 | padding: 4px 6px;
64 | margin-bottom: 10px;
65 | font-size: 14px;
66 | line-height: 20px;
67 | color: #555555;
68 | -webkit-border-radius: 4px;
69 | -moz-border-radius: 4px;
70 | border-radius: 4px;
71 | vertical-align: middle;
72 | }
73 | .tutorial button,
74 | .tutorial input,
75 | .tutorial select,
76 | .tutorial textarea {
77 | margin: 0;
78 | font-size: 100%;
79 | vertical-align: middle;
80 | }
81 | .tutorial label,
82 | .tutorial input,
83 | .tutorial button,
84 | .tutorial select,
85 | .tutorial textarea {
86 | font-size: 14px;
87 | font-weight: normal;
88 | line-height: 20px;
89 | }
90 | .tutorial code,
91 | .tutorial pre {
92 | padding: 0 3px 2px;
93 | font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
94 | font-size: 12px;
95 | color: #333333;
96 | -webkit-border-radius: 3px;
97 | -moz-border-radius: 3px;
98 | border-radius: 3px;
99 | }
100 | .tutorial code {
101 | padding: 2px 4px;
102 | color: #d14;
103 | background-color: #f7f7f9;
104 | border: 1px solid #e1e1e8;
105 | white-space: nowrap;
106 | }
107 | /*
108 | New (non dupe) styles
109 | */
110 | .tutorial p {
111 | margin-top: 0px;
112 | margin-bottom: 5px;
113 | line-height: 1.3em;
114 | }
115 | .tutorial h1,
116 | .tutorial h2,
117 | .tutorial h3,
118 | .tutorial h4 {
119 | margin-top: 5px;
120 | margin-bottom: 7px;
121 | color: #394D54;
122 | }
123 | .tutorial h3 {
124 | font-size: 24px;
125 | }
126 | .tutorial h4 {
127 | color: #008bb8;
128 | }
129 | #command .box {
130 | border: 1px grey solid;
131 | border-radius: 5px;
132 | padding: 15px;
133 | box-sizing: border-box;
134 | color: #a9a9a9;
135 | font-style: italic;
136 | }
137 | #commandShownText,
138 | #tipShownText {
139 | color: black !important;
140 | font-style: normal !important;
141 | }
142 | #command .hidden,
143 | #tips .hidden {
144 | display: none;
145 | visibility: hidden;
146 | height: 1px;
147 | width: 1px;
148 | }
149 | #ajax {
150 | position: absolute;
151 | bottom: 0;
152 | margin: 20px;
153 | }
154 | #resulttext {
155 | border-radius: 5px;
156 | box-sizing: border-box;
157 | color: #106e34;
158 | font-weight: bold;
159 | }
160 | #feedback {
161 | font-size: 0.4em;
162 | border: 1px #444444 solid;
163 | background-color: black;
164 | position: absolute;
165 | right: 0;
166 | bottom: 0px;
167 | margin-bottom: 5px;
168 | }
169 | #feedback input {
170 | margin: 5px;
171 | border-radius: 0px;
172 | position: relative;
173 | width: 400px;
174 | background-color: #dcdcdc;
175 | }
176 | #feedback button {
177 | margin: 5px 5px 5px 0;
178 | height: 30px;
179 | }
180 | #actions button {
181 | width: 45%;
182 | }
183 | .hidden {
184 | display: none;
185 | visibility: hidden;
186 | height: 1px;
187 | width: 1px;
188 | }
189 | /* styles for when it is not fullsize */
190 | #overlay {
191 | position: relative;
192 | width: 100%;
193 | }
194 | #overlay.startsize {
195 | border: 3px solid #7fafc1;
196 | height: 300px;
197 | padding: 15px;
198 | }
199 | #overlay.fullsize {
200 | position: absolute;
201 | top: 0;
202 | right: 0;
203 | bottom: 0;
204 | left: 0;
205 | width: auto;
206 | height: auto;
207 | background-color: white;
208 | z-index: 1982;
209 | }
210 | #tutorialTop {
211 | display: none;
212 | position: absolute;
213 | height: 70px;
214 | left: 0;
215 | right: 0;
216 | width: 100%;
217 | background-color: #394D54;
218 | }
219 | #tutorialTop img {
220 | margin-bottom: 20px;
221 | display: inline-block;
222 | }
223 | #tutorialTop h1,
224 | #tutorialTop h2 {
225 | display: inline-block;
226 | color: white;
227 | margin-top: 0px;
228 | margin-bottom: 0px;
229 | }
230 | #tutorialTop h1 {
231 | font-size: 22px;
232 | color: white;
233 | font-weight: normal;
234 | margin-top: 20px;
235 | }
236 | #main {
237 | position: absolute;
238 | top: 0;
239 | right: 0;
240 | bottom: 0;
241 | left: 0;
242 | }
243 | #leftside.fullsize {
244 | box-sizing: border-box;
245 | width: 35%;
246 | position: absolute;
247 | display: block;
248 | top: 0;
249 | right: 0;
250 | bottom: 0;
251 | left: 0;
252 | margin-top: 70px;
253 | overflow-y: scroll;
254 | }
255 | #rightside {
256 | margin-top: 70px;
257 | }
258 | #instructions,
259 | #results,
260 | #actions,
261 | #tips,
262 | #command {
263 | padding: 5px 10px 0 10px;
264 | }
265 | #instructions .text,
266 | #results .text,
267 | #actions .text,
268 | #tips .text,
269 | #command .text,
270 | #instructions .assignment,
271 | #results .assignment,
272 | #actions .assignment,
273 | #tips .assignment,
274 | #command .assignment,
275 | #instructions .tiptext,
276 | #results .tiptext,
277 | #actions .tiptext,
278 | #tips .tiptext,
279 | #command .tiptext {
280 | margin-left: 60px;
281 | }
282 | #instructions .text,
283 | #results .text,
284 | #actions .text,
285 | #tips .text,
286 | #command .text {
287 | font-size: 16px;
288 | }
289 | #instructions circle {
290 | fill: #FF8100;
291 | }
292 | #results {
293 | z-index: 20;
294 | background-color: #e7e7e7;
295 | width: 400px;
296 | padding: 10px;
297 | position: absolute;
298 | box-sizing: border-box;
299 | right: 0;
300 | top: 0;
301 | margin-top: 100px;
302 | }
303 | #results circle {
304 | fill: green;
305 | }
306 | #results.intermediate circle {
307 | fill: #008bb8;
308 | }
309 | #results button {
310 | position: relative;
311 | float: right;
312 | margin-top: 10px;
313 | }
314 | #instructions .assignment {
315 | margin-top: 20px;
316 | border: 2px grey solid;
317 | padding: 15px;
318 | padding-top: 5px;
319 | }
320 | .circle {
321 | position: relative;
322 | float: left;
323 | font-size: 18px;
324 | margin-top: 15px;
325 | }
326 | #terminal {
327 | box-sizing: border-box;
328 | position: absolute;
329 | display: block;
330 | top: 0;
331 | right: 0;
332 | bottom: 0;
333 | left: 35%;
334 | }
335 | #terminal.smallsize {
336 | margin: 10px;
337 | margin-bottom: 65px;
338 | }
339 | #terminal.fullsize {
340 | margin: 0;
341 | margin-right: 15px;
342 | margin-top: 70px;
343 | padding-bottom: 0px;
344 | margin-bottom: 65px;
345 | }
346 | #terminal-extenstion {
347 | box-sizing: border-box;
348 | position: absolute;
349 | display: block;
350 | top: 0;
351 | right: 0;
352 | bottom: 0;
353 | left: 35%;
354 | background-color: black;
355 | margin-bottom: 0px;
356 | }
357 | #terminal-extenstion.smallsize {
358 | margin: 10px;
359 | }
360 | #terminal-extenstion.fullsize {
361 | margin: 0;
362 | margin-top: 70px;
363 | }
364 | #terminal a {
365 | color: #AAA;
366 | text-decoration: none;
367 | font-weight: normal;
368 | }
369 | #starttext {
370 | width: 200px;
371 | margin: 10px;
372 | }
373 | .hide-when-small {
374 | display: none;
375 | }
376 | .docker-btn {
377 | border: none;
378 | color: white;
379 | }
380 | .docker-btn-large {
381 | font-size: 22px;
382 | padding: 10px 15px 10px 15px;
383 | }
384 | .docker-btn-blue {
385 | border: none;
386 | color: white;
387 | background: #008bb8;
388 | /* Old browsers */
389 |
390 | background: -moz-linear-gradient(top, #008bb8 0%, #00617f 100%);
391 | /* FF3.6+ */
392 |
393 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #008bb8), color-stop(100%, #00617f));
394 | /* Chrome,Safari4+ */
395 |
396 | background: -webkit-linear-gradient(top, #008bb8 0%, #00617f 100%);
397 | /* Chrome10+,Safari5.1+ */
398 |
399 | background: -o-linear-gradient(top, #008bb8 0%, #00617f 100%);
400 | /* Opera 11.10+ */
401 |
402 | background: -ms-linear-gradient(top, #008bb8 0%, #00617f 100%);
403 | /* IE10+ */
404 |
405 | background: linear-gradient(to bottom, #008bb8 0%, #00617f 100%);
406 | /* W3C */
407 |
408 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#008bb8', endColorstr='#00617f', GradientType=0);
409 | /* IE6-9 */
410 |
411 | }
412 | .docker-btn-blue:enabled:active {
413 | border: none;
414 | color: white;
415 | background: #00617f;
416 | /* Old browsers */
417 |
418 | background: -moz-linear-gradient(top, #00617f 0%, #008bb8 100%);
419 | /* FF3.6+ */
420 |
421 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #00617f), color-stop(100%, #008bb8));
422 | /* Chrome,Safari4+ */
423 |
424 | background: -webkit-linear-gradient(top, #00617f 0%, #008bb8 100%);
425 | /* Chrome10+,Safari5.1+ */
426 |
427 | background: -o-linear-gradient(top, #00617f 0%, #008bb8 100%);
428 | /* Opera 11.10+ */
429 |
430 | background: -ms-linear-gradient(top, #00617f 0%, #008bb8 100%);
431 | /* IE10+ */
432 |
433 | background: linear-gradient(to bottom, #00617f 0%, #008bb8 100%);
434 | /* W3C */
435 |
436 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00617f', endColorstr='#008bb8', GradientType=0);
437 | /* IE6-9 */
438 |
439 | }
440 | .docker-btn-blue:disabled {
441 | color: white;
442 | background: #4f727c;
443 | /* Old browsers */
444 |
445 | background: -moz-linear-gradient(top, #4f727c 0%, #89acb7 100%);
446 | /* FF3.6+ */
447 |
448 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #4f727c), color-stop(100%, #89acb7));
449 | /* Chrome,Safari4+ */
450 |
451 | background: -webkit-linear-gradient(top, #4f727c 0%, #89acb7 100%);
452 | /* Chrome10+,Safari5.1+ */
453 |
454 | background: -o-linear-gradient(top, #4f727c 0%, #89acb7 100%);
455 | /* Opera 11.10+ */
456 |
457 | background: -ms-linear-gradient(top, #4f727c 0%, #89acb7 100%);
458 | /* IE10+ */
459 |
460 | background: linear-gradient(to bottom, #4f727c 0%, #89acb7 100%);
461 | /* W3C */
462 |
463 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#4f727c', endColorstr='#89acb7', GradientType=0);
464 | /* IE6-9 */
465 |
466 | }
467 | #tutorialTop .closeButton {
468 | position: relative;
469 | float: right;
470 | margin: 6px;
471 | width: 18px;
472 | height: 18px;
473 | }
474 | #progress-indicator {
475 | margin-top: 20px;
476 | margin-right: 20px;
477 | float: right;
478 | cursor: hand;
479 | }
480 | .progress-marker {
481 | position: relative;
482 | float: left;
483 | margin-right: 5px;
484 | cursor: hand;
485 | }
486 | .progress-marker svg {
487 | width: 30px;
488 | height: 30px;
489 | cursor: pointer;
490 | }
491 | .progress-marker circle {
492 | fill: #eaeaea;
493 | }
494 | .progress-marker text {
495 | fill: black;
496 | }
497 | .progress-marker.complete circle {
498 | fill: green;
499 | }
500 | .progress-marker:hover circle {
501 | fill: #01b0ee;
502 | }
503 | .progress-marker:active circle {
504 | fill: #ff810a;
505 | }
506 | .progress-marker.active circle {
507 | fill: #ff810a;
508 | }
509 | .complete img {
510 | height: 45px;
511 | width: 45px;
512 | margin-top: 10px;
513 | margin-right: 10px;
514 | }
515 | .complete ol li {
516 | margin-bottom: 10px;
517 | font-weight: bold;
518 | }
519 |
--------------------------------------------------------------------------------
/docker_tutorial/static/css/tutorial-style.less:
--------------------------------------------------------------------------------
1 | @informationblue: #008BB8;
2 | @tutorialTopHeight: 60px;
3 | @greySuperLight: #E7E7E7;
4 | @topBarHeight: 70px;
5 |
6 |
7 | .debug {
8 | border: 2px red dotted;
9 | background: rgb(54, 25, 25); /* Fall-back for browsers that don't support rgba */
10 | background: rgba(54, 25, 25, .5);
11 | box-sizing: border-box;
12 | }
13 |
14 | /*
15 | Styles largely duplicates from the docker website
16 | */
17 |
18 | .tutorial {
19 | font-family: "Cabin", "Helvetica Neue", Helvetica, Arial, sans-serif;
20 | font-size: 14px;
21 | line-height: 20px;
22 | color: #333333;
23 |
24 | h1, h2, h3, h4, h5, h6 {
25 | margin: 10px 0;
26 | font-family: inherit;
27 | font-weight: normal;
28 | line-height: 20px;
29 | color: inherit;
30 | text-rendering: optimizelegibility;
31 | }
32 |
33 | h1, h2, h3 {
34 | line-height: 40px;
35 | }
36 |
37 | img {
38 | max-width: 100%;
39 | width: auto\9;
40 | height: auto;
41 | vertical-align: middle;
42 | border: 0;
43 | -ms-interpolation-mode: bicubic;
44 | }
45 |
46 | select, textarea, input[type="text"], input[type="password"], input[type="datetime"], input[type="datetime-local"], input[type="date"], input[type="month"], input[type="time"], input[type="week"], input[type="number"], input[type="email"], input[type="url"], input[type="search"], input[type="tel"], input[type="color"], .uneditable-input {
47 | display: inline-block;
48 | height: 20px;
49 | padding: 4px 6px;
50 | margin-bottom: 10px;
51 | font-size: 14px;
52 | line-height: 20px;
53 | color: #555555;
54 | -webkit-border-radius: 4px;
55 | -moz-border-radius: 4px;
56 | border-radius: 4px;
57 | vertical-align: middle;
58 | }
59 |
60 | button, input, select, textarea {
61 | margin: 0;
62 | font-size: 100%;
63 | vertical-align: middle;
64 | }
65 |
66 | label, input, button, select, textarea {
67 | font-size: 14px;
68 | font-weight: normal;
69 | line-height: 20px;
70 | }
71 |
72 | code, pre {
73 | padding: 0 3px 2px;
74 | font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
75 | font-size: 12px;
76 | color: #333333;
77 | -webkit-border-radius: 3px;
78 | -moz-border-radius: 3px;
79 | border-radius: 3px;
80 | }
81 |
82 | code {
83 | padding: 2px 4px;
84 | color: #d14;
85 | background-color: #f7f7f9;
86 | border: 1px solid #e1e1e8;
87 | white-space: nowrap;
88 | }
89 |
90 | }
91 |
92 | /*
93 | New (non dupe) styles
94 | */
95 |
96 |
97 | .tutorial {
98 | p {
99 | margin-top: 0px;
100 | margin-bottom: 5px;
101 | line-height: 1.3em;
102 | }
103 |
104 | h1, h2, h3, h4{
105 | margin-top: 5px;
106 | margin-bottom: 7px;
107 | color: #394D54;
108 | }
109 |
110 | h3 {
111 | font-size: 24px;
112 | }
113 |
114 | h4 {
115 | color: @informationblue;
116 | // margin-bottom: 5px;
117 | }
118 | }
119 |
120 |
121 | #command .box{
122 | // .debug;
123 | border: 1px grey solid;
124 | border-radius: 5px;
125 | padding: 15px;
126 | box-sizing: border-box;
127 | color: #a9a9a9;
128 | font-style: italic;
129 | // style="width: 100%; height: 100%"
130 | }
131 |
132 | #commandShownText, #tipShownText {
133 | color: black !important;
134 | font-style: normal !important;
135 | }
136 |
137 | #command .hidden, #tips .hidden {
138 | display: none;
139 | visibility: hidden;
140 | height: 1px; width: 1px;
141 | }
142 |
143 | #ajax {
144 | position: absolute;
145 | bottom: 0;
146 | margin: 20px;
147 | // border: 1px red dotted;
148 | }
149 |
150 | #resulttext {
151 | border-radius: 5px;
152 | box-sizing: border-box;
153 | color: #106e34;
154 | font-weight: bold;
155 | }
156 |
157 |
158 |
159 | #feedback {
160 | font-size: 0.4em;
161 | border: 1px #444444 solid;
162 | background-color: black;
163 | position: absolute;
164 | right: 0;
165 | bottom: 0px;
166 | margin-bottom: 5px;
167 |
168 | input {
169 | margin: 5px;
170 | border-radius: 0px;
171 | position: relative;
172 | width: 400px;
173 | background-color: #dcdcdc;
174 | }
175 | button {
176 | margin: 5px 5px 5px 0;
177 | height: 30px;
178 | }
179 | }
180 |
181 |
182 | #actions button {
183 | width: 45%;
184 | }
185 |
186 | .hidden {
187 | display: none;
188 | visibility: hidden;
189 | height: 1px;
190 | width: 1px;
191 | }
192 |
193 |
194 | /* styles for when it is not fullsize */
195 |
196 |
197 | //#overlay .hide-when-full {
198 | // display: none;
199 | //}
200 |
201 |
202 | #overlay {
203 | position: relative;
204 | width: 100%;
205 |
206 | &.startsize {
207 | border: 3px solid #7fafc1;
208 | height: 300px;
209 | padding: 15px;
210 | }
211 |
212 | &.fullsize {
213 | position: absolute;
214 | top: 0;
215 | right: 0;
216 | bottom: 0;
217 | left: 0;
218 | width: auto;
219 | height: auto;
220 |
221 | // here we set a background color for the overlay, so the elements below don't shine through
222 | background-color: white;
223 | z-index: 1982;
224 | }
225 | }
226 |
227 |
228 | #tutorialTop {
229 |
230 | display: none;
231 |
232 | position: absolute;
233 | height: @topBarHeight;
234 | left: 0;
235 | right: 0;
236 | width: 100%;
237 | background-color: #394D54;
238 |
239 | img {
240 | margin-bottom: 20px;
241 | display: inline-block;
242 | }
243 | h1, h2 {
244 | display: inline-block;
245 | color: white;
246 | margin-top: 0px;
247 | margin-bottom: 0px;
248 | }
249 |
250 | h1 {
251 | font-size: 22px;
252 | // font-size: 2.2rem;
253 | color: white;
254 | font-weight: normal;
255 | margin-top: 20px;
256 | }
257 | }
258 |
259 |
260 |
261 | #main {
262 | position: absolute;
263 | top: 0;
264 | right: 0;
265 | bottom: 0;
266 | left: 0;
267 |
268 | &.fullsize {
269 | // margin-top: 0px;
270 | // top: @topBarHeight;
271 | }
272 |
273 | }
274 |
275 | #leftside {
276 | &.fullsize {
277 | box-sizing: border-box;
278 | width: 35%;
279 | position: absolute;
280 | display: block;
281 | top: 0;
282 | right: 0;
283 | bottom: 0;
284 | left: 0;
285 |
286 | margin-top: @topBarHeight;
287 | overflow-y: scroll;
288 | // .debug;
289 | }
290 |
291 | }
292 |
293 | #rightside {
294 | margin-top: @topBarHeight;
295 | }
296 |
297 |
298 | #instructions, #results, #actions, #tips, #command {
299 | padding: 5px 10px 0 10px;
300 | .text, .assignment, .tiptext {
301 | margin-left: 60px;
302 | }
303 | .text {
304 | font-size: 16px;
305 | }
306 | }
307 |
308 | #instructions {
309 | circle { fill: #FF8100; }
310 | }
311 |
312 | #results {
313 | position: absolute;
314 | z-index: 20;
315 | background-color: @greySuperLight;
316 | width: 400px;
317 | // min-height: 130px;
318 | padding: 10px;
319 | position:absolute;
320 | box-sizing: border-box;
321 | right:0;
322 | top: 0;
323 | margin-top: @topBarHeight + 30px;
324 |
325 | circle { fill: green; }
326 |
327 | &.intermediate {
328 | circle { fill: @informationblue }
329 | }
330 |
331 | button {
332 | position: relative;
333 | float: right;
334 | margin-top: 10px;
335 | // margin-bottom: 15px;
336 | }
337 | }
338 |
339 | #instructions {
340 | // .debug;
341 | .assignment {
342 | margin-top: 20px;
343 | border: 2px grey solid;
344 | padding: 15px;
345 | padding-top: 5px;
346 | }
347 | }
348 |
349 | .circle {
350 | position: relative;
351 | float: left;
352 | font-size: 18px;
353 | margin-top: 15px;
354 | }
355 |
356 | #terminal {
357 | box-sizing: border-box;
358 | position: absolute;
359 | display: block;
360 | top: 0;
361 | right: 0;
362 | bottom: 0;
363 | left: 35%;
364 |
365 | &.smallsize {
366 | margin: 10px;
367 | // padding-bottom: 10px;
368 | margin-bottom: 65px;
369 | }
370 |
371 | &.fullsize {
372 | margin: 0;
373 | margin-right: 15px;
374 | margin-top: @topBarHeight;
375 | padding-bottom: 0px;
376 | margin-bottom: 65px;
377 | }
378 | }
379 |
380 | #terminal-extenstion {
381 | box-sizing: border-box;
382 | position: absolute;
383 | display: block;
384 | top: 0;
385 | right: 0;
386 | bottom: 0;
387 | left: 35%;
388 |
389 | background-color: black;
390 | margin-bottom: 0px;
391 |
392 | &.smallsize {
393 | margin: 10px;
394 | }
395 |
396 | &.fullsize {
397 | margin: 0;
398 | margin-top: @topBarHeight;
399 | }
400 |
401 | }
402 |
403 | #terminal a {
404 | color: #AAA;
405 | text-decoration: none;
406 | font-weight: normal;
407 | }
408 |
409 |
410 | #starttext {
411 | width: 200px;
412 | margin: 10px;
413 | }
414 |
415 | .hide-when-small {
416 | display: none;
417 | }
418 |
419 | .hide-when-full {
420 | // display: none;
421 | }
422 |
423 | .docker-btn {
424 | border: none;
425 | color: white;
426 |
427 | }
428 |
429 | .docker-btn-large {
430 | font-size: 22px;
431 | padding: 10px 15px 10px 15px;
432 | }
433 |
434 | .docker-btn-blue {
435 |
436 | .docker-btn;
437 |
438 | background: #008bb8; /* Old browsers */
439 | background: -moz-linear-gradient(top, #008bb8 0%, #00617f 100%); /* FF3.6+ */
440 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#008bb8), color-stop(100%,#00617f)); /* Chrome,Safari4+ */
441 | background: -webkit-linear-gradient(top, #008bb8 0%,#00617f 100%); /* Chrome10+,Safari5.1+ */
442 | background: -o-linear-gradient(top, #008bb8 0%,#00617f 100%); /* Opera 11.10+ */
443 | background: -ms-linear-gradient(top, #008bb8 0%,#00617f 100%); /* IE10+ */
444 | background: linear-gradient(to bottom, #008bb8 0%,#00617f 100%); /* W3C */
445 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#008bb8', endColorstr='#00617f',GradientType=0 ); /* IE6-9 */
446 | }
447 |
448 | .docker-btn-blue:enabled:active {
449 |
450 | .docker-btn;
451 |
452 | background: #00617f; /* Old browsers */
453 | background: -moz-linear-gradient(top, #00617f 0%, #008bb8 100%); /* FF3.6+ */
454 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#00617f), color-stop(100%,#008bb8)); /* Chrome,Safari4+ */
455 | background: -webkit-linear-gradient(top, #00617f 0%,#008bb8 100%); /* Chrome10+,Safari5.1+ */
456 | background: -o-linear-gradient(top, #00617f 0%,#008bb8 100%); /* Opera 11.10+ */
457 | background: -ms-linear-gradient(top, #00617f 0%,#008bb8 100%); /* IE10+ */
458 | background: linear-gradient(to bottom, #00617f 0%,#008bb8 100%); /* W3C */
459 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#00617f', endColorstr='#008bb8',GradientType=0 ); /* IE6-9 */
460 |
461 | }
462 |
463 | .docker-btn-blue:disabled {
464 | color: white;
465 |
466 | background: #4f727c; /* Old browsers */
467 | background: -moz-linear-gradient(top, #4f727c 0%, #89acb7 100%); /* FF3.6+ */
468 | background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#4f727c), color-stop(100%,#89acb7)); /* Chrome,Safari4+ */
469 | background: -webkit-linear-gradient(top, #4f727c 0%,#89acb7 100%); /* Chrome10+,Safari5.1+ */
470 | background: -o-linear-gradient(top, #4f727c 0%,#89acb7 100%); /* Opera 11.10+ */
471 | background: -ms-linear-gradient(top, #4f727c 0%,#89acb7 100%); /* IE10+ */
472 | background: linear-gradient(to bottom, #4f727c 0%,#89acb7 100%); /* W3C */
473 | filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4f727c', endColorstr='#89acb7',GradientType=0 ); /* IE6-9 */
474 | }
475 |
476 | #tutorialTop .closeButton {
477 | position: relative;
478 | float: right;
479 | margin: 6px;
480 | width: 18px;
481 | height: 18px;
482 | }
483 |
484 | #progress-indicator {
485 | margin-top: 20px;
486 | margin-right: 20px;
487 | float: right;
488 | // .debug;
489 | // width: 100px;
490 | // height: 100px;
491 | cursor: hand;
492 | }
493 |
494 | .progress-marker {
495 |
496 | position:relative;
497 | float: left;
498 | margin-right: 5px;
499 | cursor: hand;
500 | // top:5;
501 | // left:5;
502 | // z-index:1;
503 | // .debug;
504 | svg {
505 | width:30px;
506 | height:30px;
507 | cursor: pointer;
508 | }
509 |
510 | circle { fill: #eaeaea; }
511 | text { fill: black; }
512 |
513 | &.complete {
514 | circle { fill: green; }
515 | }
516 |
517 | &:hover {
518 | circle { fill: #01b0ee; }
519 | }
520 |
521 | &:active {
522 | circle { fill: #ff810a; }
523 | }
524 |
525 | &.active {
526 | circle { fill: #ff810a; }
527 | }
528 | }
529 |
530 | .complete {
531 | img {
532 | height: 45px;
533 | width: 45px;
534 | margin-top: 10px;
535 | margin-right: 10px;
536 | }
537 |
538 | ol {
539 | li {
540 | margin-bottom: 10px;
541 | font-weight: bold;
542 | }
543 | }
544 | }
545 |
--------------------------------------------------------------------------------
/dockerfile_tutorial/templates/dockerfile/level2.html:
--------------------------------------------------------------------------------
1 | {% extends 'dockerfile/_dockerfile.html' %}
2 | {% load staticfiles %}{% load google_analytics %}{% load navactive %}
3 |
4 | {% block page_js %}
5 | {{ block.super }}
6 |
7 | {% endblock page_js %}
8 |
9 |
10 | {% block content %}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Dockerfile tutorial
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
Level 2 – ENTRYPOINT, USER & EXPOSE
29 |
30 | In this section we will cover three important instructions: ENTRYPOINT, USER and EXPOSE. These commands will help you write more advanced and therefore more complex containers.
31 | At the end of this tutorial you will be able to understand and write this Dockerfile:
32 |
33 |
34 | # Memcached
35 | #
36 | # VERSION 42
37 |
38 | # use the ubuntu base image provided by dotCloud
39 | FROM ubuntu
40 |
41 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com
42 |
43 | # make sure the package repository is up to date
44 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
45 | RUN apt-get update
46 |
47 | # install memcached
48 | RUN apt-get install -y memcached
49 |
50 | # Launch memcached when launching the container
51 | ENTRYPOINT ["memcached"]
52 |
53 | # run memcached as the daemon user
54 | USER daemon
55 |
56 | # expose memcached port
57 | EXPOSE 11211
58 |
59 |
60 | Ready? Go!
61 |
62 |
Requirements
63 |
64 | Before taking this tutorial, please make sure that you completed and understand all instructions covered by the Level 1 Dockerfile tutorial (FROM, RUN, MAINTAINER, #). If this is not the case please start with Level 1 .
65 |
66 |
67 | The ENTRYPOINT instruction
68 |
69 |
The ENTRYPOINT instruction permits you to trigger a command as soon as the container starts.
70 |
71 | Whale You Be My Container?
72 |
73 |
74 | Let’s say you want your container to execute echo “Whale You Be My Container?” as soon as it starts. Then you will use:
75 |
76 |
77 | ENTRYPOINT echo “Whale You Be My Container?”
78 |
79 |
80 | Let’s try it!
81 |
82 |
83 | root@precise64:/home/vagrant/dockerfiles/tutorial1# cat Dockerfile_whale_you_be_my_container
84 | # Whale you be my container?
85 | #
86 | # VERSION 0.42
87 |
88 | # use the ubuntu base image provided by dotCloud
89 | FROM ubuntu
90 |
91 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com
92 |
93 | # say hello when the container is launched
94 | ENTRYPOINT echo "Whale you be my container"
95 | root@precise64:/home/vagrant/dockerfiles/tutorial1# docker build -t whaleyoubemycontainer - < Dockerfile_whale_you_be_my_container
96 | Uploading context 2048 bytes
97 | Step 1 : FROM ubuntu
98 | Pulling repository ubuntu
99 | ---> b750fe79269dlayersantl) from ubuntu, endpoint: https://cdn-registry-1.docker.io/v1/
100 | Step 2 : MAINTAINER Victor Coisne victor.coisne@dotcloud.com
101 | ---> Running in 2fd00b89982b
102 | ---> a2163a4bb4e6
103 | Step 3 : ENTRYPOINT echo "Whale you be my container"
104 | ---> Running in 81f46dafd81a
105 | ---> f62c1b30adad
106 | Successfully built f62c1b30adad
107 | root@precise64:/home/vagrant/dockerfiles/tutorial1# docker run whaleyoubemycontainer
108 | Whale you be my container
109 | root@precise64:/home/vagrant/dockerfiles/tutorial1#
110 |
111 |
112 | There are two syntaxes for the ENTRYPOINT instruction. Instead of command param1 param2 … you could also use the exec format:
113 |
114 |
115 | ENTRYPOINT ["echo", "Whale you be my container"]
116 |
117 |
118 | This is actually the preferred form, so we will use this form from now on.
119 |
120 |
121 | wc
122 |
123 |
124 | The ENTRYPOINT instruction helps to configure a container that you can run as an executable. That is, when you specify an ENTRYPOINT, the whole container runs as if it was just that executable. Let’s see an example with a a container running wc –l as ENTRYPOINT
125 |
126 |
127 | root@precise64:/home/vagrant/dockerfiles/tutorial1# cat Dockerfile_wc
128 | # This is wc
129 | #
130 | # VERSION 0.42
131 |
132 | # use the ubuntu base image provided by dotCloud
133 | FROM ubuntu
134 |
135 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com
136 |
137 | # count lines with wc
138 | ENTRYPOINT ["wc", "-l"]
139 | root@precise64:/home/vagrant/dockerfiles/tutorial1# docker build -t wc - < Dockerfile_wc
140 | Uploading context 2048 bytes
141 | Step 1 : FROM ubuntu
142 | ---> b750fe79269d
143 | Step 2 : MAINTAINER Victor Coisne victor.coisne@dotcloud.com
144 | ---> Using cache
145 | ---> a2163a4bb4e6
146 | Step 3 : ENTRYPOINT ["wc", "-l"]
147 | ---> Using cache
148 | ---> 52504f229d30
149 | Successfully built 52504f229d30
150 | root@precise64:/home/vagrant/dockerfiles/tutorial1# cat /etc/passwd | docker run -i wc
151 | 28
152 |
153 |
154 | memcached
155 |
156 |
157 | Remember our Dockerfile from Level 1 ? We can now add an ENTRYPOINT. Memcached needs to be run by the user daemon. So the ENTRYPOINT instruction will look like this:
158 |
159 |
ENTRYPOINT ["memcached", "-u", "daemon"]
160 |
161 | This is the resulting Dockerfile
162 |
163 |
164 | # Memcached
165 | #
166 | # VERSION 2.0
167 |
168 | # use the ubuntu base image provided by dotCloud
169 | FROM ubuntu
170 | MAINTAINER Guillaume J. Charmes guillaume@dotcloud.com
171 | # make sure the package repository is up to date
172 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
173 | RUN apt-get update
174 |
175 | # install memcached
176 | RUN apt-get install -y memcached
177 |
178 | # Launch memcached when launching the container
179 | ENTRYPOINT ["memcached", "-u", "daemon"]
180 |
181 |
182 | The USER instruction
183 |
184 |
185 | As we have seen earlier with Memcached, you may need to run the ENTRYPOINT commands as a different user than root. In the previous example, Memcached already has the option to run as another user. But it is not the case for all services/programs. Here comes the USER instruction. As you may have already understood, the USER instruction sets the username or UID to use when running the image.
186 | With this new instruction, instead of this
187 |
188 | ENTRYPOINT ["memcached", "-u", "daemon"]
189 |
190 |
191 | We can now use this
192 |
193 |
194 | ENTRYPOINT ["memcached"]
195 | USER daemon
196 |
197 |
198 | Memcached
199 |
200 |
201 | Here is our updated Memcached Dockerfile
202 |
203 |
204 | # Memcached
205 | #
206 | # VERSION 2.1
207 |
208 | # use the ubuntu base image provided by dotCloud
209 | FROM ubuntu
210 |
211 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com
212 |
213 | # make sure the package repository is up to date
214 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
215 | RUN apt-get update
216 |
217 | # install memcached
218 | RUN apt-get install -y memcached
219 |
220 | # Launch memcached when launching the container
221 | ENTRYPOINT ["memcached"]
222 |
223 | # run memcached as the daemon user
224 | USER daemon
225 |
226 |
227 | The EXPOSE instruction
228 |
229 |
230 | The EXPOSE instruction sets ports to be exposed when running the image.
231 |
232 |
233 | Memcached
234 |
235 |
236 | Memcached uses port 11211, so we have to expose this port in order to be able to talk to the Memcached container from outside the container. This is simply done with this line
237 |
238 |
239 | EXPOSE 11211
240 |
241 |
242 | By doing so, the container will sets the port 11211 to be exposed when running the Memcached image.
243 | The final Dockerfile now looks like this:
244 |
245 |
246 | # Memcached
247 | #
248 | # VERSION 2.2
249 |
250 | # use the ubuntu base image provided by dotCloud
251 | FROM ubuntu
252 |
253 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com
254 |
255 | # make sure the package repository is up to date
256 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
257 | RUN apt-get update
258 |
259 | # install memcached
260 | RUN apt-get install -y memcached
261 |
262 | # Launch memcached when launching the container
263 | ENTRYPOINT ["memcached"]
264 |
265 | # run memcached as the daemon user
266 | USER daemon
267 |
268 | # expose memcached port
269 | EXPOSE 11211
270 |
271 |
272 |
Playing with our Memcached image
273 |
274 |
275 | So, we are on a server and we need Memcached. Let's use what we have just learned :)
276 |
277 |
278 | Building the image
279 |
280 |
281 | First, let’s build the image, from the Dockerfile.
282 |
283 |
284 | root@precise64:/home/vagrant/dockerfiles/tutorial1# cat Dockerfile
285 | # Memcached
286 | #
287 | # VERSION 2.2
288 |
289 | # use the ubuntu base image provided by dotCloud
290 | FROM ubuntu
291 |
292 | MAINTAINER Victor Coisne victor.coisne@dotcloud.com
293 |
294 | # make sure the package repository is up to date
295 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
296 | RUN apt-get update
297 |
298 | # install memcached
299 | RUN apt-get install -y memcached
300 |
301 | # Launch memcached when launching the container
302 | ENTRYPOINT ["memcached"]
303 |
304 | # run memcached as the daemon user
305 | USER daemon
306 |
307 | # expose memcached port
308 | EXPOSE 11211
309 | root@precise64:/home/vagrant/dockerfiles/tutorial1# docker build -t memcached - < Dockerfile
310 | Uploading context 2560 bytes
311 | Step 1 : FROM ubuntu
312 | ---> 8dbd9e392a96
313 | Step 2 : MAINTAINER Victor Coisne victor.coisne@dotcloud.com
314 | ---> Using cache
315 | ---> dee832c11d42
316 | Step 3 : RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
317 | ---> Using cache
318 | ---> 66678b24bab4
319 | Step 4 : RUN apt-get update
320 | ---> Using cache
321 | ---> 2fbfe4f542d8
322 | Step 5 : RUN apt-get install -y memcached
323 | ---> Using cache
324 | ---> 80c7e70e69a1
325 | Step 6 : ENTRYPOINT ["memcached"]
326 | ---> Running in df59a42eeaa7
327 | ---> 090512aab2ac
328 | Step 7 : USER daemon
329 | ---> Running in f4618f8c72ec
330 | ---> 264b9e444135
331 | Step 8 : EXPOSE 11211
332 | ---> Running in e339e4cb6838
333 | ---> 42a44756989e
334 | Successfully built 42a44756989e
335 |
336 |
337 | Now let’s launch it. We use the -p flag to publish the port publicly.
338 |
339 |
340 |
root@precise64:/home/vagrant/dockerfiles/tutorial1# docker run -p 11211 memcached
341 |
342 |
343 |
344 | Memcached is running :) Now which port should we use to access it from outside the container?
345 | As we asked for it with the EXPOSE instruction, the container is exposing the 11211 port, and we used -p publish it to our system. We can use the docker ps command in order to get the port we should use from outside the container to access Memcached inside the container.
346 |
347 |
348 |
root@precise64:/home/vagrant/dockerfiles/tutorial1# docker ps
349 | ID IMAGE COMMAND CREATED STATUS PORTS NAMES
350 | 1aa7a504438c memcached:latest memcached 2 minutes ago Up 2 minutes 0.0.0.0:49153->11211/tcp blue_whale
351 |
352 |
353 | In this example the column PORTS shows that we should use the port 49153.
354 | NOTE: you could also use the docker port or the docker inspect commands to retrieve the right port number.
355 |
356 |
357 | Python
358 |
359 |
360 | We will here use python to play with Memcached, but you can use any language (Go, PHP, Ruby, …). This is a simple Python script to use your Memcached (Don’t forget to update the port number, you will probably not have the same).
361 |
362 |
363 | # run this command if import memcache fails
364 | # pip install python-memcached
365 |
366 | import memcache
367 |
368 | ip = 'localhost'
369 | port = 49153
370 |
371 | mc = memcache.Client(["{0}:{1}".format(ip, port)], debug=0)
372 | mc.set("MDM", "Guillaume J. C.")
373 | value = mc.get("MDM")
374 |
375 | print value
376 |
377 |
378 | Let’s run it!
379 |
380 |
root@precise64:/home/vagrant/dockerfiles/tutorial2# python test.py
381 | Guillaume J. C.
382 |
383 |
384 | Congratulations! You just used your first containerized Memcached server :)
385 |
386 |
387 |
Test your Dockerfile skills - Level 2
388 |
Questions
389 |
390 | What is the Dockerfile instruction to specify the base image?
391 |
The right answer was FROM
392 |
393 |
394 | Which Dockerfile instruction sets the default command for your image?
395 |
The right answer was ENTRYPOINT
396 |
397 |
398 | What is the character used to add comments in Dockerfiles?
399 |
The right answer was #
400 |
401 |
402 | Which Dockerfile instruction sets the username to use when running the image?
403 |
The right answer was USER
404 |
405 |
406 | What is the Dockerfile instruction to execute any command on the current image and commit the results?
407 |
The right answer was RUN
408 |
409 |
410 | Which Dockerfile instruction sets ports to be exposed when running the image?
411 |
The right answer was EXPOSE
412 |
413 |
414 | What is the Dockerfile instruction to specify the maintainer of the Dockerfile?
415 |
The right answer was MAINTAINER
416 |
417 |
418 | Which Dockerfile instruction lets you trigger a command as soon as the container starts?
419 |
The right answer was ENTRYPOINT
420 |
421 |
422 |
Congratulations, you made no mistake!
423 | Tell the world
424 | And try the next challenge:
DockerFill
425 |
426 |
Your Dockerfile skills are not yet perfect, try to take the time to read this tutorial again.
427 |
You're almost there! Read carefully the sections corresponding to your errors, and take the test again!
428 |
Check your answers
429 |
430 |
431 |
432 |
433 | DockerFill
434 |
435 |
436 | Your best friend Roberto Hashioka sent you a Dockerfile, but some parts were lost in the ocean. Can you find the missing parts?
437 |
438 |
439 | # Redis
440 | #
441 | # VERSION 0.42
442 |
443 | # use the ubuntu base image provided by dotCloud
444 | ub
445 |
446 | MAINT Ro Ha roberto.hashioka@dotcloud.com
447 |
448 | # make sure the package repository is up to date
449 | echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
450 | apt-get update
451 |
452 | # install wget (required for redis installation)
453 | apt-get install -y wget
454 |
455 | # install make (required for redis installation)
456 | apt-get install -y make
457 |
458 | # install gcc (required for redis installation)
459 | RUN apt-get install -y
460 |
461 | # install apache2
462 | wget http://download.redis.io/redis-stable.tar.gz
463 | tar xvzf redis-stable.tar.gz
464 | cd redis-stable && make && make install
465 |
466 | # launch redis when starting the image
467 | ["redis-server"]
468 |
469 | # run as user dameon
470 | daemon
471 |
472 | # expose port 6379
473 | 6379
474 |
475 |
476 |
Congratulations, you successfully restored Roberto's Dockerfile! You are ready to containerize the world!.
477 | Tell the world!
478 |
479 |
Wooops, there are one or more errors in the Dockerfile. Try again.
480 |
Check the Dockerfile
481 |
482 |
483 |
What's next?
484 |
485 | We will be posting Level 3 in the next few weeks. Follow us on twitter and/or enter your email address to be alerted!
486 |
487 |
488 |
489 |
490 |
Email registered.
491 |
Wrong email format. Please check your email.
492 |
Your email is already registered.
493 | Email:
Alert me when you post new levels
494 |
495 |
496 |
In the meantime, check out this blog post by Michael Crosby that describes Dockerfile Best Practices.
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 | {% endblock %}
507 |
--------------------------------------------------------------------------------
/docker_tutorial/static/js/steps.js:
--------------------------------------------------------------------------------
1 | // Generated by CoffeeScript 1.6.2
2 | /*
3 | This is the main script file. It can attach to the terminal
4 | */
5 |
6 |
7 | (function() {
8 | var COMPLETE_URL, EVENT_TYPES, buildfunction, current_question, currentquestion, drawStatusMarker, err, f, logEvent, next, previous, progressIndicator, q, question, questionNumber, questions, results, staticDockerPs, statusMarker, _i, _len;
9 |
10 | COMPLETE_URL = "/whats-next/";
11 |
12 | /*
13 | Array of question objects
14 | */
15 |
16 |
17 | staticDockerPs = "ID IMAGE COMMAND CREATED STATUS PORTS";
18 |
19 | q = [];
20 |
21 | q.push({
22 | html: "Getting started \nThere are actually two programs: The Docker daemon, which is a server process and which manages all the\ncontainers, and the Docker client, which acts as a remote control on the daemon. On most systems, like in this\nemulator, both execute on the same host.
",
23 | assignment: "Assignment \nCheck which Docker versions are running
\nThis will help you verify the daemon is running and you can connect to it. If you see which version is running\nyou know you are all set.
",
24 | tip: "Try typing docker to see the full list of accepted arguments
This emulator provides only a limited set of shell and Docker commands, so some commands may not work as expected
",
25 | command_expected: ['docker', 'version'],
26 | result: "Well done! Let's move to the next assignment.
"
27 | });
28 |
29 | q.push({
30 | html: "Searching for images \nThe easiest way to get started is to use a container image from someone else. Container images are\navailable on the Docker index, a central place to store images. You can find them online at\nindex.docker.io \nand by using the commandline
",
31 | assignment: "Assignment \nUse the commandline to search for an image called tutorial
",
32 | command_expected: ['docker', 'search', 'tutorial'],
33 | result: "You found it!
",
34 | tip: "the format is docker search <string>"
35 | });
36 |
37 | q.push({
38 | html: "Downloading container images \nContainer images can be downloaded just as easily, using docker pull.
\nFor images from the central index, the name you specify is constructed as <username>/<repository>
\nA group of special, trusted images such as the ubuntu base image can be retrieved by just their name <repository>.
",
39 | assignment: "Assignment \nPlease download the tutorial image you have just found
",
40 | command_expected: ['docker', 'pull', 'learn/tutorial'],
41 | result: "Cool. Look at the results. You'll see that Docker has downloaded a number of layers. In Docker all images (except the base image) are made up of several cumulative layers.
",
42 | tip: "Don't forget to pull the full name of the repository e.g. 'learn/tutorial'
\nLook under 'show expected command if you're stuck.
"
43 | });
44 |
45 | q.push({
46 | html: "Hello world from a container \nYou can think about containers as a process in a box. The box contains everything the process might need, so\nit has the filesystem, system libraries, shell and such, but by default none of it is started or run.
\n
You 'start' a container by running a process in it. This process is the only process run, so when\nit completes the container is fully stopped.",
47 | assignment: "
Assignment \nMake our freshly loaded container image output \"hello world\"
\nTo do so you should run 'echo' in the container and have that say \"hello world\"\n",
48 | command_expected: ["docker", "run", "learn/tutorial", "echo", "hello"],
49 | command_show: ["docker", "run", "learn/tutorial", 'echo "hello world"'],
50 | result: "
Great! Hellooooo World!
You have just started a container and executed a program inside of it, when\nthe program stopped, so did the container.",
51 | intermediateresults: [
52 | function() {
53 | return "
You seem to be almost there. Did you give the command `echo \"hello world\"` ";
54 | }, function() {
55 | return "
You've got the arguments right. Did you get the command? Try /bin/bash ?
";
56 | }
57 | ],
58 | tip: "The command docker run takes a minimum of two arguments. An image name, and the command you want to execute\nwithin that image.
\nCheck the expected command below if it does not work as expected
"
59 | });
60 |
61 | q.push({
62 | html: "Installing things in the container \nNext we are going to install a simple program (ping) in the container. The image is based upon ubuntu, so you\ncan run the command apt-get install -y ping in the container.
\nNote that even though the container stops right after a command completes, the changes are not forgotten.
",
63 | assignment: "Assignment \nInstall 'ping' on top of the learn/tutorial image.
",
64 | command_expected: ["docker", "run", "learn/tutorial", "apt-get", "install", "-y", "ping"],
65 | result: "That worked! You have installed a program on top of a base image. Your changes to the filesystem have been\nkept, but are not yet saved.
",
66 | intermediateresults: [
67 | function() {
68 | return "Not specifying -y on the apt-get install command will work for ping, because it has no other dependencies, but\nit will fail when apt-get wants to install dependencies. To get into the habit, please add -y after apt-get.
";
69 | }
70 | ],
71 | tip: "Don't forget to use -y for noninteractive mode installation
\nNot specifying -y on the apt-get install command will fail for most commands because it expects you to accept\n(y/n) but you cannot respond.\n
"
72 | });
73 |
74 | q.push({
75 | html: "Save your changes \nAfter you make changes (by running a command inside a container), you probably want to save those changes.\nThis will enable you to later start from this point onwards.
\nWith Docker, the process of saving the state is called committing . Commit basically saves the difference\nbetween the old image and the new state. The result is a new layer.
",
76 | assignment: "Assignment \nFirst use docker ps -l to find the ID of the container you created by installing ping.
\nThen save (commit) this container with the repository name 'learn/ping'
",
77 | command_expected: ["docker", "commit", "698", "learn/ping"],
78 | command_show: ["docker", "commit", "698", 'learn/ping'],
79 | result: "That worked! Please take note that Docker has returned a new ID. This id is the image id .
",
80 | intermediateresults: [
81 | function() {
82 | return "You have not specified the correct repository name to commit to (learn/ping). This works, but giving your images a name\nmakes them much easier to work with.";
83 | }
84 | ],
85 | tip: "\nGiving just docker commit will show you the possible arguments. \nYou will need to specify the container to commit by the ID you found \nYou don't need to copy (type) the entire ID. Three or four characters are usually enough. \n "
86 | });
87 |
88 | q.push({
89 | html: "Run your new image \nNow you have basically setup a complete, self contained environment with the 'ping' program installed.
\nYour image can now be run on any host that runs Docker.
\nLets run this image on this machine.
",
90 | assignment: "Assignment \nRun the ping program to ping www.google.com
\n",
91 | command_expected: ["docker", "run", 'learn/ping', 'ping', 'google.com'],
92 | result: "That worked! Note that normally you can use Ctrl-C to disconnect. The container will keep running. This\ncontainer will disconnect automatically.
",
93 | intermediateresults: [
94 | function() {
95 | return "You have not specified a repository name. This is not wrong, but giving your images a name\nmake them much easier to work with.";
96 | }
97 | ],
98 | tip: "\nMake sure to use the repository name learn/ping to run ping with \n "
99 | });
100 |
101 | q.push({
102 | html: "Check your running image \nYou now have a running container. Let's see what is going on.
\nUsing docker ps we can see a list of all running containers, and using docker inspect\nwe can see all sorts of useful information about this container.
",
103 | assignment: "Assignment \nFind the container id of the running container, and then inspect the container using docker inspect .
\n",
104 | command_expected: ["docker", "inspect", "efe"],
105 | result: "Success! Have a look at the output. You can see the ip-address, status and other information.
",
106 | intermediateresults: [
107 | function() {
108 | return "You have not specified a repository name. This is not wrong, but giving your images a name\nmake them much easier to work with.";
109 | }
110 | ],
111 | tip: "\nRemember you can use a partial match of the image id \n ",
112 | currentDockerPs: "ID IMAGE COMMAND CREATED STATUS PORTS\nefefdc74a1d5 learn/ping:latest ping www.google.com 37 seconds ago Up 36 seconds"
113 | });
114 |
115 | q.push({
116 | html: "Push your image to the index \nNow you have verified that your application container works, you can share it.
\nRemember you pulled (downloaded) the learn/tutorial image from the index? You can also share your built images\nto the index by pushing (uploading) them to there. That way you can easily retrieve them for re-use and share them\nwith others.
",
117 | assignment: "Assignment \nPush your container image learn/ping to the index
\n",
118 | command_expected: ["will_never_be_valid"],
119 | command_show: ["docker", "push", "learn/ping"],
120 | result: "",
121 | intermediateresults: [
122 | function() {
123 | var data;
124 |
125 | $('#instructions .assignment').hide();
126 | $('#tips, #command').hide();
127 | $('#instructions .text').html("\n
Congratulations! \n
You have mastered the basic docker commands!
\n
Did you enjoy this tutorial? Share it!
\n
\n \n \n \n
\n
Your next steps \n
\n Register for news and updates on Docker (opens in new window) \n Follow us on twitter (opens in new window) \n Close this tutorial, and continue with the rest of the getting started. \n \n
- Or -
\n
Continue to learn about the way to automatically build your containers from a file.
Start Dockerfile tutorial
\n\n
");
128 | data = {
129 | type: EVENT_TYPES.complete
130 | };
131 | logEvent(data);
132 | return "All done!. You are now pushing a container image to the index. You can see that push, just like pull, happens layer by layer.
";
133 | }
134 | ],
135 | tip: "\ndocker images will show you which images are currently on your host \ndocker pushis the command to push images \nYou can only push images to your own namespace, this emulator is logged in as user 'learn' \n\n ",
136 | finishedCallback: function() {
137 | webterm.clear();
138 | return webterm.echo(myTerminal());
139 | }
140 | });
141 |
142 | questions = [];
143 |
144 | /*
145 | Register the terminal
146 | */
147 |
148 |
149 | this.webterm = $('#terminal').terminal(interpreter, basesettings);
150 |
151 | EVENT_TYPES = {
152 | none: "none",
153 | start: "start",
154 | command: "command",
155 | next: "next",
156 | peek: "peek",
157 | feedback: "feedback",
158 | complete: "complete"
159 | };
160 |
161 | /*
162 | Sending events to the server
163 | */
164 |
165 |
166 | logEvent = function(data, feedback) {
167 | var ajax_load, callback, loadUrl;
168 |
169 | ajax_load = "loading......";
170 | loadUrl = "/tutorial/api/";
171 | if (!feedback) {
172 | callback = function(responseText) {
173 | return $("#ajax").html(responseText);
174 | };
175 | } else {
176 | callback = function(responseText) {
177 | results.set("Thank you for your feedback! We appreciate it!", true);
178 | $('#feedbackInput').val("");
179 | return $("#ajax").html(responseText);
180 | };
181 | }
182 | if (!data) {
183 | data = {
184 | type: EVENT_TYPES.none
185 | };
186 | }
187 | data.question = current_question;
188 | $("#ajax").html(ajax_load);
189 | return $.post(loadUrl, data, callback, "html");
190 | };
191 |
192 | /*
193 | Event handlers
194 | */
195 |
196 |
197 | $('#buttonNext').click(function(e) {
198 | this.setAttribute('disabled', 'disabled');
199 | console.log(e);
200 | return next();
201 | });
202 |
203 | $('#buttonFinish').click(function() {
204 | return window.open(COMPLETE_URL);
205 | });
206 |
207 | $('#buttonPrevious').click(function() {
208 | previous();
209 | return $('#results').hide();
210 | });
211 |
212 | $('#leftside').bind('mousewheel', function(event, delta, deltaX, deltaY) {
213 | this.scrollTop += deltaY * -30;
214 | return event.preventDefault();
215 | });
216 |
217 | $('#feedbackSubmit').click(function() {
218 | var data, feedback;
219 |
220 | feedback = $('#feedbackInput').val();
221 | data = {
222 | type: EVENT_TYPES.feedback,
223 | feedback: feedback
224 | };
225 | return logEvent(data, feedback = true);
226 | });
227 |
228 | $('#fullSizeOpen').click(function() {
229 | return goFullScreen();
230 | });
231 |
232 | this.goFullScreen = function() {
233 | console.debug("going to fullsize mode");
234 | $('.togglesize').removeClass('startsize').addClass('fullsize');
235 | $('.hide-when-small').css({
236 | display: 'inherit'
237 | });
238 | $('.hide-when-full').css({
239 | display: 'none'
240 | });
241 | next(0);
242 | webterm.resize();
243 | return setTimeout(function() {
244 | return logEvent({
245 | type: EVENT_TYPES.start
246 | });
247 | }, 3000);
248 | };
249 |
250 | $('#fullSizeClose').click(function() {
251 | return leaveFullSizeMode();
252 | });
253 |
254 | this.leaveFullSizeMode = function() {
255 | console.debug("leaving full-size mode");
256 | $('.togglesize').removeClass('fullsize').addClass('startsize');
257 | $('.hide-when-small').css({
258 | display: 'none'
259 | });
260 | $('.hide-when-full').css({
261 | display: 'inherit'
262 | });
263 | return webterm.resize();
264 | };
265 |
266 | $('#command').click(function() {
267 | var data;
268 |
269 | if (!$('#commandHiddenText').hasClass('hidden')) {
270 | $('#commandHiddenText').addClass("hidden").hide();
271 | $('#commandShownText').hide().removeClass("hidden").fadeIn();
272 | }
273 | data = {
274 | type: EVENT_TYPES.peek
275 | };
276 | return logEvent(data);
277 | });
278 |
279 | /*
280 | Navigation amongst the questions
281 | */
282 |
283 |
284 | current_question = 0;
285 |
286 | next = function(which) {
287 | var data;
288 |
289 | $('#marker-' + current_question).addClass("complete").removeClass("active");
290 | if (!which && which !== 0) {
291 | current_question++;
292 | } else {
293 | current_question = which;
294 | }
295 | questions[current_question]();
296 | results.clear();
297 | this.webterm.focus();
298 | if (!$('#commandShownText').hasClass('hidden')) {
299 | $('#commandShownText').addClass("hidden");
300 | $('#commandHiddenText').removeClass("hidden").show();
301 | }
302 | history.pushState({}, "", "#" + current_question);
303 | data = {
304 | 'type': EVENT_TYPES.next
305 | };
306 | logEvent(data);
307 | $('#marker-' + current_question).removeClass("complete").addClass("active");
308 | $('#question-number').find('text').get(0).textContent = current_question;
309 | $('#instructions .assignment').show();
310 | $('#tips, #command').show();
311 | };
312 |
313 | previous = function() {
314 | current_question--;
315 | questions[current_question]();
316 | results.clear();
317 | this.webterm.focus();
318 | };
319 |
320 | results = {
321 | set: function(htmlText, intermediate) {
322 | if (intermediate) {
323 | console.debug("intermediate text received");
324 | $('#results').addClass('intermediate');
325 | $('#buttonNext').hide();
326 | } else {
327 | $('#buttonNext').show();
328 | }
329 | return window.setTimeout((function() {
330 | $('#resulttext').html(htmlText);
331 | $('#results').fadeIn();
332 | return $('#buttonNext').removeAttr('disabled');
333 | }), 300);
334 | },
335 | clear: function() {
336 | $('#resulttext').html("");
337 | return $('#results').fadeOut('slow');
338 | }
339 | };
340 |
341 | /*
342 | Transform question objects into functions
343 | */
344 |
345 |
346 | buildfunction = function(q) {
347 | var _q;
348 |
349 | _q = q;
350 | return function() {
351 | console.debug("function called");
352 | $('#instructions').hide().fadeIn();
353 | $('#instructions .text').html(_q.html);
354 | $('#instructions .assignment').html(_q.assignment);
355 | $('#tipShownText').html(_q.tip);
356 | if (_q.command_show) {
357 | $('#commandShownText').html(_q.command_show.join(' '));
358 | } else {
359 | $('#commandShownText').html(_q.command_expected.join(' '));
360 | }
361 | if (_q.currentDockerPs != null) {
362 | window.currentDockerPs = _q.currentDockerPs;
363 | } else {
364 | window.currentDockerPs = staticDockerPs;
365 | }
366 | if (_q.finishedCallback != null) {
367 | window.finishedCallback = q.finishedCallback;
368 | } else {
369 | window.finishedCallback = function() {
370 | return "";
371 | };
372 | }
373 | window.immediateCallback = function(input, stop) {
374 | var data, doNotExecute;
375 |
376 | if (stop === true) {
377 | doNotExecute = true;
378 | } else {
379 | doNotExecute = false;
380 | }
381 | if (doNotExecute !== true) {
382 | console.log(input);
383 | data = {
384 | 'type': EVENT_TYPES.command,
385 | 'command': input.join(' '),
386 | 'result': 'fail'
387 | };
388 | if (input.containsAllOfTheseParts(_q.command_expected)) {
389 | data.result = 'success';
390 | setTimeout((function() {
391 | this.webterm.disable();
392 | return $('#buttonNext').focus();
393 | }), 1000);
394 | results.set(_q.result);
395 | console.debug("contains match");
396 | } else {
397 | console.debug("wrong command received");
398 | }
399 | logEvent(data);
400 | }
401 | };
402 | window.intermediateResults = function(input) {
403 | var intermediate;
404 |
405 | if (_q.intermediateresults) {
406 | return results.set(_q.intermediateresults[input](), intermediate = true);
407 | }
408 | };
409 | };
410 | };
411 |
412 | statusMarker = $('#progress-marker-0');
413 |
414 | progressIndicator = $('#progress-indicator');
415 |
416 | drawStatusMarker = function(i) {
417 | var marker;
418 |
419 | if (i === 0) {
420 | marker = statusMarker;
421 | } else {
422 | marker = statusMarker.clone();
423 | marker.appendTo(progressIndicator);
424 | }
425 | marker.attr("id", "marker-" + i);
426 | marker.find('text').get(0).textContent = i;
427 | return marker.click(function() {
428 | return next(i);
429 | });
430 | };
431 |
432 | questionNumber = 0;
433 |
434 | for (_i = 0, _len = q.length; _i < _len; _i++) {
435 | question = q[_i];
436 | f = buildfunction(question);
437 | questions.push(f);
438 | drawStatusMarker(questionNumber);
439 | questionNumber++;
440 | }
441 |
442 | /*
443 | Initialization of program
444 | */
445 |
446 |
447 | if (window.location.hash) {
448 | try {
449 | currentquestion = window.location.hash.split('#')[1].toNumber();
450 | next(currentquestion);
451 | } catch (_error) {
452 | err = _error;
453 | questions[0]();
454 | }
455 | } else {
456 | questions[0]();
457 | }
458 |
459 | $('#results').hide();
460 |
461 | }).call(this);
462 |
--------------------------------------------------------------------------------
/docker_tutorial/static/js/steps.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | This is the main script file. It can attach to the terminal
3 | ###
4 |
5 | COMPLETE_URL = "/whats-next/"
6 |
7 |
8 | ###
9 | Array of question objects
10 | ###
11 |
12 | staticDockerPs = """
13 | ID IMAGE COMMAND CREATED STATUS PORTS
14 | """
15 |
16 |
17 | q = []
18 | q.push ({
19 | html: """
20 | Getting started
21 | There are actually two programs: The Docker daemon, which is a server process and which manages all the
22 | containers, and the Docker client, which acts as a remote control on the daemon. On most systems, like in this
23 | emulator, both execute on the same host.
24 | """
25 | assignment: """
26 | Assignment
27 | Check which Docker versions are running
28 | This will help you verify the daemon is running and you can connect to it. If you see which version is running
29 | you know you are all set.
30 | """
31 | tip: "Try typing docker to see the full list of accepted arguments
32 | This emulator provides only a limited set of shell and Docker commands, so some commands may not work as expected
"
33 | command_expected: ['docker', 'version']
34 | result: """Well done! Let's move to the next assignment.
"""
35 | })
36 |
37 | q.push ({
38 | html: """
39 | Searching for images
40 | The easiest way to get started is to use a container image from someone else. Container images are
41 | available on the Docker index, a central place to store images. You can find them online at
42 | index.docker.io
43 | and by using the commandline
44 | """
45 | assignment: """
46 | Assignment
47 | Use the commandline to search for an image called tutorial
48 | """
49 | command_expected: ['docker', 'search', 'tutorial']
50 | result: """You found it!
"""
51 | tip: "the format is docker search <string>"
52 | })
53 |
54 | q.push ({
55 | html: """
56 | Downloading container images
57 | Container images can be downloaded just as easily, using docker pull.
58 | For images from the central index, the name you specify is constructed as <username>/<repository>
59 | A group of special, trusted images such as the ubuntu base image can be retrieved by just their name <repository>.
60 | """
61 | assignment:
62 | """
63 | Assignment
64 | Please download the tutorial image you have just found
65 | """
66 | command_expected: ['docker', 'pull', 'learn/tutorial']
67 | result: """Cool. Look at the results. You'll see that Docker has downloaded a number of layers. In Docker all images (except the base image) are made up of several cumulative layers.
"""
68 | tip: """Don't forget to pull the full name of the repository e.g. 'learn/tutorial'
69 | Look under 'show expected command if you're stuck.
70 | """
71 | })
72 |
73 |
74 | q.push ({
75 | html: """
76 | Hello world from a container
77 | You can think about containers as a process in a box. The box contains everything the process might need, so
78 | it has the filesystem, system libraries, shell and such, but by default none of it is started or run.
79 |
You 'start' a container by running a process in it. This process is the only process run, so when
80 | it completes the container is fully stopped.
81 | """
82 | assignment: """
83 |
Assignment
84 | Make our freshly loaded container image output "hello world"
85 | To do so you should run 'echo' in the container and have that say "hello world"
86 |
87 | """
88 | command_expected: ["docker", "run", "learn/tutorial", "echo", "hello"]
89 | command_show: ["docker", "run", "learn/tutorial", 'echo "hello world"']
90 |
91 | result: """
Great! Hellooooo World!
You have just started a container and executed a program inside of it, when
92 | the program stopped, so did the container."""
93 | intermediateresults: [
94 | () -> """
You seem to be almost there. Did you give the command `echo "hello world"` """,
95 | () -> """
You've got the arguments right. Did you get the command? Try /bin/bash ?
"""
96 | ]
97 | tip: """
98 | The command docker run takes a minimum of two arguments. An image name, and the command you want to execute
99 | within that image.
100 | Check the expected command below if it does not work as expected
101 | """
102 | })
103 |
104 | q.push ({
105 | html: """
106 | Installing things in the container
107 | Next we are going to install a simple program (ping) in the container. The image is based upon ubuntu, so you
108 | can run the command apt-get install -y ping in the container.
109 | Note that even though the container stops right after a command completes, the changes are not forgotten.
110 | """
111 | assignment: """
112 | Assignment
113 | Install 'ping' on top of the learn/tutorial image.
114 | """
115 | command_expected: ["docker", "run", "learn/tutorial", "apt-get", "install", "-y", "ping"]
116 | result: """That worked! You have installed a program on top of a base image. Your changes to the filesystem have been
117 | kept, but are not yet saved.
"""
118 | intermediateresults: [
119 | () -> """Not specifying -y on the apt-get install command will work for ping, because it has no other dependencies, but
120 | it will fail when apt-get wants to install dependencies. To get into the habit, please add -y after apt-get.
""",
121 | ]
122 | tip: """
123 | Don't forget to use -y for noninteractive mode installation
124 | Not specifying -y on the apt-get install command will fail for most commands because it expects you to accept
125 | (y/n) but you cannot respond.
126 |
127 | """
128 | })
129 |
130 | q.push ({
131 | html: """
132 | Save your changes
133 | After you make changes (by running a command inside a container), you probably want to save those changes.
134 | This will enable you to later start from this point onwards.
135 | With Docker, the process of saving the state is called committing . Commit basically saves the difference
136 | between the old image and the new state. The result is a new layer.
137 | """
138 | assignment: """
139 | Assignment
140 | First use docker ps -l to find the ID of the container you created by installing ping.
141 | Then save (commit) this container with the repository name 'learn/ping'
142 | """
143 | command_expected: ["docker", "commit", "698", "learn/ping"]
144 | command_show: ["docker", "commit", "698", 'learn/ping']
145 | result: """That worked! Please take note that Docker has returned a new ID. This id is the image id .
"""
146 | intermediateresults: [ () -> """You have not specified the correct repository name to commit to (learn/ping). This works, but giving your images a name
147 | makes them much easier to work with."""]
148 | tip: """
149 | Giving just docker commit will show you the possible arguments.
150 | You will need to specify the container to commit by the ID you found
151 | You don't need to copy (type) the entire ID. Three or four characters are usually enough.
152 | """
153 | })
154 |
155 |
156 | q.push ({
157 | html: """
158 | Run your new image
159 | Now you have basically setup a complete, self contained environment with the 'ping' program installed.
160 | Your image can now be run on any host that runs Docker.
161 | Lets run this image on this machine.
162 | """
163 | assignment: """
164 | Assignment
165 | Run the ping program to ping www.google.com
166 |
167 | """
168 | command_expected: ["docker", "run", 'learn/ping', 'ping', 'google.com' ]
169 | result: """That worked! Note that normally you can use Ctrl-C to disconnect. The container will keep running. This
170 | container will disconnect automatically.
"""
171 | intermediateresults: [ () -> """You have not specified a repository name. This is not wrong, but giving your images a name
172 | make them much easier to work with."""]
173 | tip: """
174 | Make sure to use the repository name learn/ping to run ping with
175 | """
176 | })
177 |
178 |
179 |
180 |
181 | q.push ({
182 | html: """
183 | Check your running image
184 | You now have a running container. Let's see what is going on.
185 | Using docker ps we can see a list of all running containers, and using docker inspect
186 | we can see all sorts of useful information about this container.
187 | """
188 | assignment: """
189 | Assignment
190 | Find the container id of the running container, and then inspect the container using docker inspect .
191 |
192 | """
193 | command_expected: ["docker", "inspect", "efe" ]
194 | result: """Success! Have a look at the output. You can see the ip-address, status and other information.
"""
195 | intermediateresults: [ () -> """You have not specified a repository name. This is not wrong, but giving your images a name
196 | make them much easier to work with."""]
197 | tip: """
198 | Remember you can use a partial match of the image id
199 | """
200 | currentDockerPs:
201 | """
202 | ID IMAGE COMMAND CREATED STATUS PORTS
203 | efefdc74a1d5 learn/ping:latest ping www.google.com 37 seconds ago Up 36 seconds
204 | """
205 |
206 | })
207 |
208 |
209 |
210 | q.push ({
211 | html: """
212 | Push your image to the index
213 | Now you have verified that your application container works, you can share it.
214 | Remember you pulled (downloaded) the learn/tutorial image from the index? You can also share your built images
215 | to the index by pushing (uploading) them to there. That way you can easily retrieve them for re-use and share them
216 | with others.
217 | """
218 | assignment: """
219 | Assignment
220 | Push your container image learn/ping to the index
221 |
222 | """
223 | #command_expected: ["docker", "push", "learn/ping"]
224 | command_expected: ["will_never_be_valid"]
225 | command_show: ["docker", "push", "learn/ping"]
226 | result: """"""
227 | intermediateresults:
228 | [
229 | () ->
230 | $('#instructions .assignment').hide()
231 | $('#tips, #command').hide()
232 |
233 | $('#instructions .text').html("""
234 |
235 |
Congratulations!
236 |
You have mastered the basic docker commands!
237 |
Did you enjoy this tutorial? Share it!
238 |
239 |
240 |
241 |
242 |
243 |
Your next steps
244 |
245 | Register for news and updates on Docker (opens in new window)
246 | Follow us on twitter (opens in new window)
247 | Close this tutorial, and continue with the rest of the getting started.
248 |
249 |
- Or -
250 |
Continue to learn about the way to automatically build your containers from a file.
Start Dockerfile tutorial
251 |
252 |
253 | """)
254 |
255 |
256 | data = { type: EVENT_TYPES.complete }
257 | logEvent(data)
258 |
259 | return """All done!. You are now pushing a container image to the index. You can see that push, just like pull, happens layer by layer.
"""
260 | ]
261 | tip: """
262 | docker images will show you which images are currently on your host
263 | docker pushis the command to push images
264 | You can only push images to your own namespace, this emulator is logged in as user 'learn'
265 |
266 | """
267 | finishedCallback: () ->
268 | webterm.clear()
269 | webterm.echo( myTerminal() )
270 |
271 |
272 | })
273 |
274 |
275 | # the index arr
276 | questions = []
277 |
278 |
279 |
280 |
281 | ###
282 | Register the terminal
283 | ###
284 |
285 | @webterm = $('#terminal').terminal(interpreter, basesettings)
286 |
287 |
288 |
289 |
290 | EVENT_TYPES =
291 | none: "none"
292 | start: "start"
293 | command: "command"
294 | next: "next"
295 | peek: "peek"
296 | feedback: "feedback"
297 | complete: "complete"
298 |
299 |
300 |
301 | ###
302 | Sending events to the server
303 | ###
304 |
305 | logEvent = (data, feedback) ->
306 | ajax_load = "loading......";
307 | loadUrl = "/tutorial/api/";
308 | if not feedback
309 | callback = (responseText) -> $("#ajax").html(responseText)
310 | else
311 | callback = (responseText) ->
312 | results.set("Thank you for your feedback! We appreciate it!", true)
313 | $('#feedbackInput').val("")
314 | $("#ajax").html(responseText)
315 |
316 | if not data then data = {type: EVENT_TYPES.none}
317 | data.question = current_question
318 |
319 |
320 | $("#ajax").html(ajax_load);
321 | $.post(loadUrl, data, callback, "html")
322 |
323 |
324 |
325 | ###
326 | Event handlers
327 | ###
328 |
329 |
330 | ## next
331 | $('#buttonNext').click (e) ->
332 |
333 | # disable the button to prevent spacebar to hit it when typing in the terminal
334 | this.setAttribute('disabled','disabled')
335 | console.log(e)
336 | next()
337 |
338 | $('#buttonFinish').click ->
339 | window.open(COMPLETE_URL)
340 |
341 | ## previous
342 | $('#buttonPrevious').click ->
343 | previous()
344 | $('#results').hide()
345 |
346 | ## Stop mousewheel on left side, and manually move it.
347 | $('#leftside').bind('mousewheel',
348 | (event, delta, deltaX, deltaY) ->
349 | this.scrollTop += deltaY * -30
350 | event.preventDefault()
351 | )
352 |
353 | ## submit feedback
354 | $('#feedbackSubmit').click ->
355 | feedback = $('#feedbackInput').val()
356 | data = { type: EVENT_TYPES.feedback, feedback: feedback}
357 | logEvent(data, feedback=true)
358 |
359 | ## fullsize
360 | $('#fullSizeOpen').click ->
361 | goFullScreen()
362 |
363 | @goFullScreen = () ->
364 | console.debug("going to fullsize mode")
365 | $('.togglesize').removeClass('startsize').addClass('fullsize')
366 |
367 | $('.hide-when-small').css({ display: 'inherit' })
368 | $('.hide-when-full').css({ display: 'none' })
369 |
370 | next(0)
371 |
372 | webterm.resize()
373 |
374 | # send the next event after a short timeout, so it doesn't come at the same time as the next() event
375 | # in the beginning. Othewise two sessions will appear to have been started.
376 | # This will make the order to appear wrong, but that's not much of an issue.
377 |
378 | setTimeout( () ->
379 | logEvent( { type: EVENT_TYPES.start } )
380 | , 3000)
381 |
382 |
383 | ## leave fullsize
384 | $('#fullSizeClose').click ->
385 | leaveFullSizeMode()
386 |
387 | @leaveFullSizeMode = () ->
388 | console.debug "leaving full-size mode"
389 |
390 | $('.togglesize').removeClass('fullsize').addClass('startsize')
391 |
392 | $('.hide-when-small').css({ display: 'none' })
393 | $('.hide-when-full').css({ display: 'inherit' })
394 |
395 | webterm.resize()
396 |
397 | ## click on tips
398 | $('#command').click () ->
399 | if not $('#commandHiddenText').hasClass('hidden')
400 | $('#commandHiddenText').addClass("hidden").hide()
401 | $('#commandShownText').hide().removeClass("hidden").fadeIn()
402 |
403 | data = { type: EVENT_TYPES.peek }
404 | logEvent(data)
405 |
406 |
407 |
408 | ###
409 | Navigation amongst the questions
410 | ###
411 |
412 |
413 | current_question = 0
414 | next = (which) ->
415 | # before increment clear style from previous question progress indicator
416 | $('#marker-' + current_question).addClass("complete").removeClass("active")
417 |
418 | if not which and which != 0
419 | current_question++
420 | else
421 | current_question = which
422 |
423 | questions[current_question]()
424 | results.clear()
425 | @webterm.focus()
426 |
427 | if not $('#commandShownText').hasClass('hidden')
428 | $('#commandShownText').addClass("hidden")
429 | $('#commandHiddenText').removeClass("hidden").show()
430 |
431 | # enable history navigation
432 | history.pushState({}, "", "#" + current_question);
433 | data = { 'type': EVENT_TYPES.next }
434 | logEvent(data)
435 |
436 | # change the progress indicator
437 | $('#marker-' + current_question).removeClass("complete").addClass("active")
438 |
439 | $('#question-number').find('text').get(0).textContent = current_question
440 |
441 | # show in the case they were hidden by the complete step.
442 | $('#instructions .assignment').show()
443 | $('#tips, #command').show()
444 |
445 |
446 | return
447 |
448 | previous = () ->
449 | current_question--
450 | questions[current_question]()
451 | results.clear()
452 | @webterm.focus()
453 | return
454 |
455 |
456 |
457 | results = {
458 | set: (htmlText, intermediate) ->
459 | if intermediate
460 | console.debug "intermediate text received"
461 | $('#results').addClass('intermediate')
462 | $('#buttonNext').hide()
463 | else
464 | $('#buttonNext').show()
465 |
466 | window.setTimeout ( () ->
467 | $('#resulttext').html(htmlText)
468 | $('#results').fadeIn()
469 | $('#buttonNext').removeAttr('disabled')
470 | ), 300
471 |
472 | clear: ->
473 | $('#resulttext').html("")
474 | $('#results').fadeOut('slow')
475 | # $('#buttonNext').addAttr('disabled')
476 | }
477 |
478 |
479 |
480 | ###
481 | Transform question objects into functions
482 | ###
483 |
484 | buildfunction = (q) ->
485 | _q = q
486 | return ->
487 | console.debug("function called")
488 |
489 | $('#instructions').hide().fadeIn()
490 | $('#instructions .text').html(_q.html)
491 | $('#instructions .assignment').html(_q.assignment)
492 | $('#tipShownText').html(_q.tip)
493 | if _q.command_show
494 | $('#commandShownText').html(_q.command_show.join(' '))
495 | else
496 | $('#commandShownText').html(_q.command_expected.join(' '))
497 |
498 | if _q.currentDockerPs?
499 | window.currentDockerPs = _q.currentDockerPs
500 | else
501 | window.currentDockerPs = staticDockerPs
502 |
503 | if _q.finishedCallback?
504 | window.finishedCallback = q.finishedCallback
505 | else
506 | window.finishedCallback = () -> return ""
507 |
508 | window.immediateCallback = (input, stop) ->
509 | if stop == true # prevent the next event from happening
510 | doNotExecute = true
511 | else
512 | doNotExecute = false
513 |
514 | if doNotExecute != true
515 | console.log (input)
516 |
517 | data = { 'type': EVENT_TYPES.command, 'command': input.join(' '), 'result': 'fail' }
518 |
519 | # Was like this: if not input.switches.containsAllOfThese(_q.arguments)
520 | if input.containsAllOfTheseParts(_q.command_expected)
521 | data.result = 'success'
522 |
523 | setTimeout( ( ->
524 | @webterm.disable()
525 | $('#buttonNext').focus()
526 | ), 1000)
527 |
528 | results.set(_q.result)
529 | console.debug "contains match"
530 | else
531 | console.debug("wrong command received")
532 |
533 | # call function to submit data
534 | logEvent(data)
535 | return
536 |
537 | window.intermediateResults = (input) ->
538 | if _q.intermediateresults
539 | results.set(_q.intermediateresults[input](), intermediate=true)
540 | return
541 |
542 |
543 | statusMarker = $('#progress-marker-0')
544 | progressIndicator = $('#progress-indicator')#
545 |
546 | drawStatusMarker = (i) ->
547 | if i == 0
548 | marker = statusMarker
549 | else
550 | marker = statusMarker.clone()
551 | marker.appendTo(progressIndicator)
552 |
553 | marker.attr("id", "marker-" + i)
554 | marker.find('text').get(0).textContent = i
555 | marker.click( -> next(i) )
556 |
557 |
558 | questionNumber = 0
559 | for question in q
560 | f = buildfunction(question)
561 | questions.push(f)
562 | drawStatusMarker(questionNumber)
563 | questionNumber++
564 |
565 |
566 | ###
567 | Initialization of program
568 | ###
569 |
570 | #load the first question, or if the url hash is set, use that
571 | if (window.location.hash)
572 | try
573 | currentquestion = window.location.hash.split('#')[1].toNumber()
574 | # questions[currentquestion]()
575 | # current_question = currentquestion
576 | next(currentquestion)
577 |
578 | catch err
579 | questions[0]()
580 | else
581 | questions[0]()
582 |
583 | $('#results').hide()
584 |
585 |
--------------------------------------------------------------------------------
/dockerfile_tutorial/templates/dockerfile/level1.html:
--------------------------------------------------------------------------------
1 | {% extends 'dockerfile/_dockerfile.html' %}
2 | {% load staticfiles %}{% load google_analytics %}{% load navactive %}
3 |
4 | {% block page_js %}
5 | {{ block.super }}
6 |
7 | {% endblock page_js %}
8 |
9 |
10 | {% block content %}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Dockerfile tutorial
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
Level 1 - FROM, RUN, #, MAINTAINER
27 |
28 |
29 | In this section we will cover the most important instructions: FROM and RUN .
30 | We will also see how to use the docker build command and how to specify the maintainer of the Dockerfile and how to insert comments.
31 |
32 |
33 |
Dockerfile instructions
34 |
35 | All Dockerfile instructions look like this:
36 |
37 |
38 |
39 |
INSTRUCTION arguments
40 |
41 |
42 |
43 | Instructions are not case sensitive, but we recommend to use capital letters.
44 |
45 |
46 |
The FROM instruction
47 |
48 |
49 | The first instruction of every Dockerfile needs to be the FROM instruction
50 |
51 |
52 |
FROM image
53 |
54 |
55 | It sets the base image for subsequent instructions.
56 | Example:
57 |
58 |
FROM ubuntu
59 |
60 | In this example, we chose ubuntu as our base image. ubuntu is a “General use Ubuntu base image” provided by the Docker team.
61 | You can find a list of available images from the command line by using the docker search command.
62 | Example:
63 |
64 |
65 | root@precise64:/home/vagrant/dockerfiles# docker search centos
66 | Found 14 results matching your query ("centos")
67 | NAME DESCRIPTION
68 | centos
69 | backjlack/centos-6.4-x86_64
70 | creack/centos
71 | mbkan/lamp centos with ssh, LAMP, PHPMyAdmin(root password 'toor' and MySQL root password 'sysadmin'). Auto starts sshd, MySQL and Apache. Just launch us...
72 | zenosslabs/centos
73 | slantview/centos-chef-solo CentOS 6.4 with chef-solo.
74 | tchap/centos-epel The official centos image having EPEL added among its repositories.
75 | backjlack/centos This repository contains the following images: Centos 5.9 (more to be added soon)
76 | oss17888/centos6.4
77 | markl/centos
78 | troygoode/centos-node-hello
79 | austin/centos-base
80 | estrai/centos32 Centos 5.9 32-bit based on an OpenVZ image.
81 | comodit/centos
82 |
83 |
84 | So let’s say you want to use centos as you base image, you can for instance use the following line:
85 |
86 | FROM centos
87 |
88 |
89 | You can also browse available images from Docker and the community online: index.docker.io
90 |
91 |
The RUN instruction
92 |
93 | The RUN instruction will execute any commands on the current image and commit the results. The resulting committed image will be used for the next step in the Dockerfile.
94 | Layering RUN instructions and generating commits conforms to the core concepts of Docker where commits are cheap and containers can be created from any point in an image’s history, much like source control.
95 |
96 | RUN command
97 |
98 |
99 | RUN command is equivalent to the docker commands: docker run image command + docker commit container_id,
100 | Where image would be replaced automatically with the current image, and container_id would be the result of the previous RUN instruction.
101 | Example:
102 |
103 | RUN apt-get install -y memcached
104 |
105 |
106 | As we saw earlier, we need to have the
FROM instruction before any
RUN instruction, so that docker knows from which base image to start running the instruction on. For instance:
107 |
108 |
109 | FROM ubuntu
110 | RUN apt-get install -y memcached
111 |
112 |
113 | This example creates an image with memcached installed on the ubuntu image. It is the equivalent to the following command lines:
114 |
115 |
116 | root@precise64:/home/vagrant/dockerfiles# docker run ubuntu apt-get install -y memcached
117 | […]
118 | [=> container id is 9fb69e798e67]
119 | root@precise64:/home/vagrant/dockerfiles# docker commit 9fb69e798e67
120 | 6742949a1bae
121 |
122 |
123 |
docker build
124 |
125 | Once you have your Dockerfile ready, you can use the docker build command to create your image from it. There are different ways to use this command:
126 |
127 |
128 | If your Dockerfile is in your current directory you can use docker build .
129 |
130 |
131 | From stdin. For instance: docker build - < Dockerfile
132 |
133 |
134 | From GitHub. For instance: docker build github.com/creack/docker-firefox
135 | In this last example, docker will clone the GitHub repository and use it as context. The Dockerfile at the root of the repository will be used to create the image.
136 |
137 |
138 |
139 | Let’s run our last Dockerfile example.
140 |
141 |
142 | root@precise64:/home/vagrant/dockerfiles# cat Dockerfile
143 | FROM ubuntu
144 | RUN apt-get install -y memcached
145 | root@precise64:/home/vagrant/dockerfiles# docker build .
146 | Uploading context 20480 bytes
147 | Step 1 : FROM ubuntu
148 | Pulling repository ubuntu
149 | Pulling image 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c (precise) from ubuntu
150 | Pulling image b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc (quantal) from ubuntu
151 | Pulling b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc metadata
152 | Pulling b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc fs layer
153 | Downloading 10.24 kB/10.24 kB (100%)
154 | Pulling 27cf784147099545 metadata
155 | Pulling 27cf784147099545 fs layer
156 | Downloading 10.24 kB/10.24 kB (100%)
157 | Pulling 27cf784147099545 metadata
158 | Pulling 27cf784147099545 fs layer
159 | Downloading 94.86 MB/94.86 MB (100%)
160 | ---> 8dbd9e392a96
161 | Step 2 : RUN apt-get install -y memcached
162 | ---> Running in dedc7af62d9f
163 | Reading package lists...
164 | Building dependency tree...
165 | The following extra packages will be installed:
166 | libclass-isa-perl libevent-2.0-5 libgdbm3 libswitch-perl netbase perl
167 | perl-modules
168 | Suggested packages:
169 | libcache-memcached-perl libmemcached perl-doc libterm-readline-gnu-perl
170 | libterm-readline-perl-perl make libpod-plainer-perl
171 | The following NEW packages will be installed:
172 | libclass-isa-perl libevent-2.0-5 libgdbm3 libswitch-perl memcached netbase
173 | perl perl-modules
174 | 0 upgraded, 8 newly installed, 0 to remove and 0 not upgraded.
175 | Need to get 8070 kB of archives.
176 | After this operation, 32.8 MB of additional disk space will be used.
177 | Get:1 http://archive.ubuntu.com/ubuntu/ precise/main libgdbm3 amd64 1.8.3-10 [35.3 kB]
178 | Get:2 http://archive.ubuntu.com/ubuntu/ precise/main netbase all 4.47ubuntu1 [15.0 kB]
179 | Get:3 http://archive.ubuntu.com/ubuntu/ precise/main libclass-isa-perl all 0.36-3 [11.9 kB]
180 | Get:4 http://archive.ubuntu.com/ubuntu/ precise/main libevent-2.0-5 amd64 2.0.16-stable-1 [127 kB]
181 | Get:5 http://archive.ubuntu.com/ubuntu/ precise/main perl-modules all 5.14.2-6ubuntu2 [3369 kB]
182 | Get:6 http://archive.ubuntu.com/ubuntu/ precise/main perl amd64 5.14.2-6ubuntu2 [4417 kB]
183 | Get:7 http://archive.ubuntu.com/ubuntu/ precise/main libswitch-perl all 2.16-2 [19.2 kB]
184 | Get:8 http://archive.ubuntu.com/ubuntu/ precise/main memcached amd64 1.4.13-0ubuntu2 [75.3 kB]
185 | debconf: delaying package configuration, since apt-utils is not installed
186 | Fetched 8070 kB in 7s (1136 kB/s)
187 | Selecting previously unselected package libgdbm3.
188 | (Reading database ... 7545 files and directories currently installed.)
189 | Unpacking libgdbm3 (from .../libgdbm3_1.8.3-10_amd64.deb) ...
190 | Selecting previously unselected package netbase.
191 | Unpacking netbase (from .../netbase_4.47ubuntu1_all.deb) ...
192 | Selecting previously unselected package libclass-isa-perl.
193 | Unpacking libclass-isa-perl (from .../libclass-isa-perl_0.36-3_all.deb) ...
194 | Selecting previously unselected package libevent-2.0-5.
195 | Unpacking libevent-2.0-5 (from .../libevent-2.0-5_2.0.16-stable-1_amd64.deb) ...
196 | Selecting previously unselected package perl-modules.
197 | Unpacking perl-modules (from .../perl-modules_5.14.2-6ubuntu2_all.deb) ...
198 | Selecting previously unselected package perl.
199 | Unpacking perl (from .../perl_5.14.2-6ubuntu2_amd64.deb) ...
200 | Selecting previously unselected package libswitch-perl.
201 | Unpacking libswitch-perl (from .../libswitch-perl_2.16-2_all.deb) ...
202 | Selecting previously unselected package memcached.
203 | Unpacking memcached (from .../memcached_1.4.13-0ubuntu2_amd64.deb) ...
204 | Setting up libgdbm3 (1.8.3-10) ...
205 | Setting up netbase (4.47ubuntu1) ...
206 | Setting up libclass-isa-perl (0.36-3) ...
207 | Setting up libevent-2.0-5 (2.0.16-stable-1) ...
208 | Setting up perl-modules (5.14.2-6ubuntu2) ...
209 | Setting up perl (5.14.2-6ubuntu2) ...
210 | update-alternatives: using /usr/bin/prename to provide /usr/bin/rename (rename) in auto mode.
211 | Setting up memcached (1.4.13-0ubuntu2) ...
212 | Starting memcached: memcached.
213 | Setting up libswitch-perl (2.16-2) ...
214 | Processing triggers for libc-bin ...
215 | ldconfig deferred processing now taking place
216 | ---> 1dcfa24c8ca6
217 | Successfully built 1dcfa24c8ca6
218 |
219 |
220 | You can see your newly created image 1dcfa24c8ca6 in the ouput of docker images:
221 |
222 |
223 | root@precise64:/home/vagrant/dockerfiles# docker images
224 | REPOSITORY TAG ID CREATED SIZE
225 | ubuntu 12.04 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
226 | ubuntu 12.10 b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
227 | ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
228 | ubuntu precise 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
229 | ubuntu quantal b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
230 | <none> <none> 1275893fed44 9 minutes ago 12.3 kB (virtual 566.4 MB)
231 | <none> <none> 1dcfa24c8ca6 About a minute ago 52.27 MB (virtual 183.8 MB)
232 |
233 |
234 | Note that it does not have a name yet. Let’s name it with the command docker tag.
235 |
236 |
237 | root@precise64:/home/vagrant/dockerfiles# docker tag 1dcfa24c8ca6 memcached
238 | root@precise64:/home/vagrant/dockerfiles# docker images
239 | REPOSITORY TAG ID CREATED SIZE
240 | ubuntu 12.04 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
241 | ubuntu 12.10 b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
242 | ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
243 | ubuntu precise 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
244 | ubuntu quantal b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
245 | memcached latest 1dcfa24c8ca6 4 minutes ago 52.27 MB (virtual 183.8 MB)
246 | <none> <none> 1275893fed44 12 minutes ago 12.3 kB (virtual 566.4 MB)
247 |
248 |
249 | The good news is that docker build comes with the option -t with which you can directly tag the (last) image created with the Dockerfile. So we could have used docker build -t memcached . directly.
250 |
251 |
252 | root@precise64:/home/vagrant/dockerfiles# docker build -t memcached_new .
253 | Uploading context 20480 bytes
254 | Step 1 : FROM ubuntu
255 | […]
256 | Step 2 : RUN apt-get install -y memcached
257 | […]
258 | Successfully built 1dcfa24c8ca6
259 | root@precise64:/home/vagrant/dockerfiles# docker images
260 | REPOSITORY TAG ID CREATED SIZE
261 | ubuntu 12.04 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
262 | ubuntu 12.10 b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
263 | ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
264 | ubuntu precise 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
265 | ubuntu quantal b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
266 | memcached_new latest 1dcfa24c8ca6 4 minutes ago 52.27 MB (virtual 183.8 MB)
267 | <none> <none> 1275893fed44 4 minutes ago 12.3 kB (virtual 566.4 MB)
268 |
269 |
270 |
Creating a memcached image with a Dockerfile
271 |
272 | We have written almost all of it in the previous sections.
273 |
274 |
275 | FROM ubuntu
276 | RUN apt-get install -y memcached
277 |
278 |
279 | But we want to ensure that the ubuntu package repository is up to date. With the command line we would do:
280 |
281 | root@precise64:/home/vagrant/dockerfiles# echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
282 | root@precise64:/home/vagrant/dockerfiles# apt-get update
283 |
284 |
285 | Within the Dockerfile, we will use the RUN instruction.
286 |
287 |
288 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
289 | RUN apt-get update
290 |
291 |
292 | So our final Dockerfile looks like this:
293 |
294 |
295 | FROM ubuntu
296 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
297 | RUN apt-get update
298 | RUN apt-get install -y memcached
299 |
300 |
301 | Note that this Dockerfile would be equivalent to the following command lines:
302 |
303 | root@precise64:/home/vagrant/dockerfiles# docker run ubuntu echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
304 | […]
305 | [=> container id is 72d2a39bc64d]
306 | root@precise64:/home/vagrant/dockerfiles# docker commit 72d2a39bc64d
307 | d7a97544c0c7
308 | root@precise64:/home/vagrant/dockerfiles# docker run d7a97544c0c7 apt-get update
309 | […]
310 | [=> container id is 66b22a0ef7c9]
311 | root@precise64:/home/vagrant/dockerfiles# docker commit 66b22a0ef7c9
312 | a5f142a604b3
313 | root@precise64:/home/vagrant/dockerfiles# docker run a5f142a604b3 apt-get install -y memcached
314 | […]
315 | [=> container id is 9fb69e798e67]
316 | root@precise64:/home/vagrant/dockerfiles# docker commit 9fb69e798e67
317 | 6742949a1bae
318 |
319 |
320 | Let’s create our image. We will use the -t option with docker build in order to give a name to our image.
321 |
322 |
323 | root@precise64:/home/vagrant/dockerfiles# cat Dockerfile
324 | FROM ubuntu
325 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
326 | RUN apt-get update
327 | RUN apt-get install -y memcached
328 | root@precise64:/home/vagrant/dockerfiles# docker build -t brand_new_memcached - < Dockerfile
329 | Uploading context 2048 bytes
330 | Step 1 : FROM ubuntu
331 | ---> 8dbd9e392a96
332 | Step 2 : RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
333 | ---> Using cache
334 | ---> 913c2344b312
335 | Step 3 : RUN apt-get update
336 | ---> Using cache
337 | ---> 0a77b6e48393
338 | Step 4 : RUN apt-get install -y memcached
339 | ---> Running in d1eadc19edc6
340 | Reading package lists...
341 | Building dependency tree...
342 | The following extra packages will be installed:
343 | libclass-isa-perl libevent-2.0-5 libgdbm3 libswitch-perl netbase perl
344 | perl-modules
345 | […]
346 | Starting memcached: memcached.
347 | Setting up libswitch-perl (2.16-2) ...
348 | Processing triggers for libc-bin ...
349 | ldconfig deferred processing now taking place
350 | ---> 782a37534312
351 | Successfully built 782a37534312
352 |
353 |
354 | The id of the image is 782a37534312 but it already has a name as we can verify with docker images:
355 |
356 |
357 | root@precise64:/home/vagrant/dockerfiles# docker images
358 | REPOSITORY TAG ID CREATED SIZE
359 | memcached_new latest 1dcfa24c8ca6 12 minutes ago 52.27 MB (virtual 183.8 MB)
360 | brand_new_memcached latest 782a37534312 54 seconds ago 74.7 MB (virtual 350.7 MB)
361 | ubuntu 12.04 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
362 | ubuntu 12.10 b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
363 | ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
364 | ubuntu precise 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
365 | ubuntu quantal b750fe79269d 4 months ago 24.65 kB (virtual 180.1 MB)
366 | memcached latest 1dcfa24c8ca6 12 minutes ago 52.27 MB (virtual 183.8 MB)
367 | <none> <none> 1275893fed44 21 minutes ago 12.3 kB (virtual 566.4 MB)
368 |
369 |
370 | Congratulations! You just created your first image with a Dockerfile :)
371 |
372 | You can verify that memcached is installed in your image by running these commands:
373 |
374 | root@precise64:/home/vagrant/dockerfiles# docker run -i -t brand_new_memcached /bin/bash
375 | root@c28a2c4be825:/# memcached
376 | can't run as root without the -u switch
377 |
378 |
379 |
Commenting
380 |
381 | Code should always be commented. The same rule applies to the Dockerfile.
382 | A character # at the beginning of a line defines a comment. The comments are not exectuted. Comments are not mandatory, but we recommend using them to:
383 |
384 | Give a title and/or describe the purpose of the Dockerfile
385 | Give the version of your Dockerfile
386 | Comment individual Dockerfile lines and instructions
387 | …
388 |
389 | So the Dockerfile to create a memcached image becomes:
390 |
391 | # Memcached
392 | #
393 | # VERSION 1.0
394 |
395 | # use the ubuntu base image provided by dotCloud
396 | FROM ubuntu
397 |
398 | # make sure the package repository is up to date
399 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
400 | RUN apt-get update
401 |
402 | # install memcached
403 | RUN apt-get install -y memcached
404 |
405 |
406 |
The MAINTAINER instruction
407 |
408 | As you could expect, the MAINTAINER instruction is used to specify name and contact information of the maintainer of the Dockerfile.
409 | Example:
410 |
411 |
412 | MAINTAINER Guillaume J. Charmes, guillaume@dotcloud.com
413 |
414 |
415 | The final version of the Dockerfile becomes
416 |
417 | # Memcached
418 | #
419 | # VERSION 1.0
420 |
421 | # use the ubuntu base image provided by dotCloud
422 | FROM ubuntu
423 | MAINTAINER Guillaume J. Charmes, guillaume@dotcloud.com
424 | # make sure the package repository is up to date
425 | RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list
426 | RUN apt-get update
427 |
428 | # install memcached
429 | RUN apt-get install -y memcached
430 |
431 |
432 |
Test your Dockerfile skills - Level 1
433 |
Questions
434 |
435 |
436 | What is the Dockerfile instruction to specify the base image ?
437 |
The right answer was FROM
438 |
439 | What is the Dockerfile instruction to execute any commands on the current image and commit the results?
440 |
The right answer was RUN
441 |
442 | What is the Dockerfile instruction to specify the maintainer of the Dockerfile?
443 |
The right answer was MAINTAINER
444 |
445 | What is the character used to add comment in Dockerfiles?
446 |
The right answer was #
447 |
448 |
Congratulations, you made no mistake!
449 | Tell the world
450 | And try the next challenge:
Fill the Dockerfile
451 |
452 |
Your Dockerfile skills are not yet perfect, try to take the time to read this tutorial again.
453 |
You're almost there! Read carefully the sections corresponding to your errors, and take the test again!
454 |
Check your answers
455 |
456 |
457 |
458 |
Fill the Dockerfile
459 |
460 | Your best friend Eric Bardin sent you a Dockerfile, but some parts were lost in the ocean. Can you find the missing parts?
461 |
462 |
485 |
486 |
Congratulations, you successfully restored Eric's Dockerfile! You are ready to containerize the world!.
487 | Tell the world!
488 |
489 |
Wooops, there are one or more errors in the Dockerfile. Try again.
490 |
Check the Dockerfile
491 |
492 |
What's next?
493 |
494 |
In the next level, we will go into more detail about how to specify which command should be executed when the container starts,
495 | which user to use, and how expose a particular port.
496 |
497 |
Go to the next level
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 | {% endblock %}
509 |
--------------------------------------------------------------------------------
/docker_tutorial/static/js/terminal.coffee:
--------------------------------------------------------------------------------
1 | ###
2 | Please note the javascript is being fully generated from coffeescript.
3 | So make your changes in the .coffee file.
4 | Thatcher Peskens
5 | _
6 | ,_(')<
7 | \___)
8 |
9 | ###
10 |
11 | do @myTerminal = ->
12 |
13 | # Which terminal emulator version are we
14 | EMULATOR_VERSION = "0.1.3"
15 |
16 |
17 | @basesettings = {
18 | prompt: 'you@tutorial:~$ ',
19 | greetings: """
20 | Welcome to the interactive Docker tutorial
21 | """
22 |
23 | }
24 |
25 | ###
26 | Callback definitions. These can be overridden by functions anywhere else
27 | ###
28 |
29 | @preventDefaultCallback = false
30 |
31 | @immediateCallback = (command) ->
32 | console.debug("immediate callback from #{command}")
33 | return
34 |
35 | @finishedCallback = (command) ->
36 | console.debug("finished callback from #{command}")
37 | return
38 |
39 | @intermediateResults = (string) ->
40 | console.debug("sent #{string}")
41 | return
42 |
43 | @currentDockerPs = ""
44 |
45 | ###
46 | Base interpreter
47 | ###
48 |
49 | @interpreter = (input, term) ->
50 | inputs = input.split(" ")
51 | command = inputs[0]
52 |
53 | if command is 'hi'
54 | term.echo 'hi there! What is your name??'
55 | term.push (command, term) ->
56 | term.echo command + ' is a pretty name'
57 |
58 | else if command is 'shell'
59 | term.push (command, term) ->
60 | if command is 'cd'
61 | bash(term, inputs)
62 | , {prompt: '> $ '}
63 |
64 | else if command is 'r'
65 | location.reload('forceGet')
66 |
67 | else if command is '#'
68 | term.echo 'which question?'
69 |
70 | else if command is 'test'
71 | term.echo 'I have to keep testing myself.'
72 |
73 | else if command is 'cd'
74 | bash(term, inputs)
75 |
76 | else if command is "docker"
77 | Docker(term, inputs)
78 |
79 | else if command is "help"
80 | term.echo help
81 |
82 | else if command is "ls"
83 | term.echo "This is an emulator, not a shell. Try following the instructions."
84 |
85 | else if command is "colors"
86 | for DockerCommand, description of DockerCommands
87 | term.echo ("[[b;#fff;]" + DockerCommand + "] - " + description + "")
88 |
89 | else if command is "pull"
90 | term.echo '[[b;#fff;]some text]'
91 | wait(term, 5000, true)
92 | alert term.get_output()
93 |
94 | return
95 |
96 | ## finally
97 | else if command
98 | term.echo "#{inputs[0]}: command not found"
99 |
100 | immediateCallback(inputs)
101 |
102 | ### =======================================
103 | Common utils
104 | ======================================= ###
105 |
106 | String.prototype.beginsWith = (string) ->
107 | ###
108 | Check if 'this' string starts with the inputstring.
109 | ###
110 | return(this.indexOf(string) is 0)
111 |
112 | Array.prototype.containsAllOfThese = (inputArr) ->
113 | ###
114 | This function compares all of the elements in the inputArr
115 | and checks them one by one if they exist in 'this'. When it
116 | finds an element to not exist, it returns false.
117 | ###
118 | me = this
119 | valid = false
120 |
121 | if inputArr
122 | valid = inputArr.every( (value) ->
123 | if me.indexOf(value) == -1
124 | return false
125 | else
126 | return true
127 | )
128 | return valid
129 |
130 |
131 | Array.prototype.containsAllOfTheseParts = (inputArr) ->
132 | ###
133 | This function is like containsAllofThese, but also matches partial strings.
134 | ###
135 |
136 | me = this
137 | if inputArr
138 | valid = inputArr.every( (value) ->
139 | for item in me
140 | if item.match(value)
141 | return true
142 |
143 | return false
144 | )
145 | return valid
146 |
147 |
148 | parseInput = (inputs) ->
149 | command = inputs[1]
150 | switches = []
151 | switchArg = false
152 | switchArgs = []
153 | imagename = ""
154 | commands = []
155 | j = 0
156 |
157 | # parse args
158 | for input in inputs
159 | if input.startsWith('-') and imagename == ""
160 | switches.push(input)
161 | if switches.length > 0
162 | if not ['-i', '-t', '-d'].containsAllOfThese([input])
163 | switchArg = true
164 | else if switchArg == true
165 | # reset switchArg
166 | switchArg = false
167 | switchArgs.push(input)
168 | else if j > 1 and imagename == ""
169 | # match wrong names
170 | imagename = input
171 | else if imagename != ""
172 | commands.push (input)
173 | else
174 | # nothing?
175 | j++
176 |
177 | parsed_input = {
178 | 'switches': switches.sortBy(),
179 | 'switchArgs': switchArgs,
180 | 'imageName': imagename,
181 | 'commands': commands,
182 | }
183 | return parsed_input
184 |
185 |
186 | util_slow_lines = (term, paragraph, keyword, finishedCallback) ->
187 |
188 | if keyword
189 | lines = paragraph(keyword).split("\n")
190 | else
191 | lines = paragraph.split("\n")
192 |
193 | term.pause()
194 | i = 0
195 | # function calls itself after timeout is done, untill
196 | # all lines are finished
197 | foo = (lines) ->
198 | self.setTimeout ( ->
199 | if lines[i]
200 | term.echo (lines[i])
201 | i++
202 | foo(lines)
203 | else
204 | term.resume()
205 | finishedCallback()
206 | ), 1000
207 |
208 | foo(lines)
209 |
210 |
211 | wait = (term, time, dots) ->
212 | term.echo "starting to wait"
213 | interval_id = self.setInterval ( -> dots ? term.insert '.'), 500
214 |
215 | self.setTimeout ( ->
216 | self.clearInterval(interval_id)
217 | output = term.get_command()
218 | term.echo output
219 | term.echo "done "
220 | ), time
221 |
222 | ###
223 | Bash program
224 | ###
225 |
226 | bash = (term, inputs) ->
227 | echo = term.echo
228 | insert = term.insert
229 |
230 | if not inputs[1]
231 | console.log("none")
232 |
233 | else
234 | argument = inputs[1]
235 | if argument.beginsWith('..')
236 | echo "-bash: cd: #{argument}: Permission denied"
237 | else
238 | echo "-bash: cd: #{argument}: No such file or directory"
239 |
240 | ###
241 | Docker program
242 | ###
243 |
244 | Docker = (term, inputs) ->
245 |
246 | echo = term.echo
247 | insert = term.insert
248 | callback = () -> @finishedCallback(inputs)
249 | command = inputs[1]
250 |
251 | # no command
252 | if not inputs[1]
253 | console.debug "no args"
254 | echo Docker_cmd
255 | for DockerCommand, description of DockerCommands
256 | echo "[[b;#fff;]" + DockerCommand + "]" + description + ""
257 |
258 | # Command commit
259 | else if inputs[1] is "commit"
260 | if inputs.containsAllOfTheseParts(['docker', 'commit', '698', 'learn/ping'])
261 | util_slow_lines(term, commit_containerid, "", callback )
262 | else if inputs.containsAllOfTheseParts(['docker', 'commit', '698'])
263 | util_slow_lines(term, commit_containerid, "", callback )
264 | intermediateResults(0)
265 | else if inputs.containsAllOfTheseParts(['docker', 'commit']) and inputs[2]
266 | echo commit_id_does_not_exist(inputs[2])
267 | else
268 | echo commit
269 |
270 | else if inputs[1] is "do"
271 | term.push('do', {prompt: "do $ "})
272 |
273 | else if inputs[1] is "logo"
274 | echo Docker_logo
275 |
276 | else if inputs[1] is "images"
277 | echo images
278 |
279 | else if inputs[1] is "inspect"
280 | if inputs[2] and inputs[2].match('ef')
281 | echo inspect_ping_container
282 | else if inputs[2]
283 | echo inspect_no_such_container(inputs[2])
284 | else
285 | echo inspect
286 |
287 | # command ps
288 | else if command is "ps"
289 | if inputs.containsAllOfThese(['-l'])
290 | echo ps_l
291 | else if inputs.containsAllOfThese(['-a'])
292 | echo ps_a
293 | else
294 | echo currentDockerPs
295 | else if inputs[1] is "push"
296 | if inputs[2] is "learn/ping"
297 | util_slow_lines(term, push_container_learn_ping, "", callback )
298 | intermediateResults(0)
299 | return
300 | else if inputs[2]
301 | echo push_wrong_name
302 | else
303 | echo push
304 |
305 |
306 | # Command run
307 | else if inputs[1] is "run"
308 | # parse all input so we have a json object
309 | parsed_input = parseInput(inputs)
310 |
311 | switches = parsed_input.switches
312 | swargs = parsed_input.switchArgs
313 | imagename = parsed_input.imageName
314 | commands = parsed_input.commands
315 |
316 | console.log "commands"
317 | console.log commands
318 | console.log "switches"
319 | console.log switches
320 |
321 | console.log "parsed input"
322 | console.log parsed_input
323 | console.log "imagename: #{imagename}"
324 |
325 | if imagename is "ubuntu"
326 | if switches.containsAllOfTheseParts(['-i', '-t'])
327 | if commands.containsAllOfTheseParts(['bash'])
328 | term.push ( (command, term) ->
329 | if command
330 | echo """this shell is not implemented. Enter 'exit' to exit."""
331 | return
332 | ), {prompt: 'root@687bbbc4231b:/# '}
333 | else
334 | echo run_image_wrong_command(commands)
335 | else
336 | echo run_flag_defined_not_defined(switches)
337 | else if imagename is "learn/tutorial"
338 | if switches.length > 0
339 | echo run_learn_no_command
340 | intermediateResults(0)
341 | else if commands[0] is "/bin/bash"
342 | echo run_learn_tutorial_echo_hello_world(commands)
343 | intermediateResults(2)
344 | else if commands[0] is "echo"
345 | echo run_learn_tutorial_echo_hello_world(commands)
346 | else if commands.containsAllOfThese(['apt-get', 'install', '-y', 'iputils-ping'])
347 | echo run_apt_get_install_iputils_ping
348 | else if commands.containsAllOfThese(['apt-get', 'install', 'iputils-ping'])
349 | echo run_apt_get_install_iputils_ping
350 | # intermediateResults(0)
351 | else if commands.containsAllOfThese(['apt-get', 'install', 'ping'])
352 | echo run_apt_get_install_iputils_ping
353 | # intermediateResults(0)
354 | else if commands.containsAllOfThese(['apt-get', 'install'])
355 | i = commands.length - 1
356 | echo run_apt_get_install_unknown_package( commands[i] )
357 | # intermediateResults(0)
358 | else if commands[0] is "apt-get"
359 | echo run_apt_get
360 | else if commands[0]
361 | echo run_image_wrong_command(commands[0])
362 | else
363 | echo run_learn_no_command
364 |
365 | else if imagename is "learn/ping"
366 | if commands.containsAllOfTheseParts(["ping", "google.com"])
367 | util_slow_lines(term, run_ping_www_google_com, "", callback )
368 | else if commands[0] is "ping" and commands[1]
369 | echo run_ping_not_google(commands[1])
370 | else if commands[0] is "ping"
371 | echo ping
372 | else if commands[0]
373 | echo "#{commands[0]}: command not found"
374 | else
375 | echo run_learn_no_command
376 |
377 | else if imagename
378 | echo run_notfound(inputs[2])
379 | else
380 | console.log("run")
381 | echo run_cmd
382 |
383 | else if inputs[1] is "search"
384 | if keyword = inputs[2]
385 | if keyword is "ubuntu"
386 | echo search_ubuntu
387 | else if keyword is "tutorial"
388 | echo search_tutorial
389 | else
390 | echo search_no_results(inputs[2])
391 | else echo search
392 |
393 | else if inputs[1] is "pull"
394 | if keyword = inputs[2]
395 | if keyword is 'ubuntu'
396 | result = util_slow_lines(term, pull_ubuntu, "", callback )
397 | else if keyword is 'learn/tutorial'
398 | result = util_slow_lines(term, pull_tutorial, "", callback )
399 | else
400 | util_slow_lines(term, pull_no_results, keyword)
401 | else
402 | echo pull
403 |
404 | else if inputs[1] is "version"
405 | # console.log(version)
406 | echo docker_version()
407 |
408 |
409 | else if DockerCommands[inputs[1]]
410 | echo "#{inputs[1]} is a valid argument, but not implemented"
411 |
412 | else
413 | echo Docker_cmd
414 | for DockerCommand, description of DockerCommands
415 | echo "[[b;#fff;]" + DockerCommand + "]" + description + ""
416 |
417 | # return empty value because otherwise coffeescript will return last var
418 | return
419 |
420 | ###
421 | Some default variables / commands
422 |
423 | All items are sorted by alphabet
424 | ###
425 |
426 | Docker_cmd = \
427 | """
428 | Usage: Docker [OPTIONS] COMMAND [arg...]
429 | -H="127.0.0.1:4243": Host:port to bind/connect to
430 |
431 | A self-sufficient runtime for linux containers.
432 |
433 | Commands:
434 |
435 | """
436 |
437 | DockerCommands =
438 | "attach": " Attach to a running container"
439 | "build": " Build a container from a Dockerfile"
440 | "commit": " Create a new image from a container's changes"
441 | "diff": " Inspect changes on a container's filesystem"
442 | "export": " Stream the contents of a container as a tar archive"
443 | "history": " Show the history of an image"
444 | "images": " List images"
445 | "import": " Create a new filesystem image from the contents of a tarball"
446 | "info": " Display system-wide information"
447 | "insert": " Insert a file in an image"
448 | "inspect": " Return low-level information on a container"
449 | "kill": " Kill a running container"
450 | "login": " Register or Login to the Docker registry server"
451 | "logs": " Fetch the logs of a container"
452 | "port": " Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"
453 | "ps": " List containers"
454 | "pull": " Pull an image or a repository from the Docker registry server"
455 | "push": " Push an image or a repository to the Docker registry server"
456 | "restart": " Restart a running container"
457 | "rm": " Remove a container"
458 | "rmi": " Remove an image"
459 | "run": " Run a command in a new container"
460 | "search": " Search for an image in the Docker index"
461 | "start": " Start a stopped container"
462 | "stop": " Stop a running container"
463 | "tag": " Tag an image into a repository"
464 | "version": " Show the Docker version information"
465 | "wait": " Block until a container stops, then print its exit code"
466 |
467 | run_switches =
468 | "-p": ['port']
469 | "-t": []
470 | "-i": []
471 | "-h": ['hostname']
472 |
473 | commit = \
474 | """
475 | Usage: Docker commit [OPTIONS] CONTAINER [REPOSITORY [TAG]]
476 |
477 | Create a new image from a container's changes
478 |
479 | -author="": Author (eg. "John Hannibal Smith "
480 | -m="": Commit message
481 | -run="": Config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs": ["22"]}')
482 | """
483 |
484 | commit_id_does_not_exist = (keyword) ->
485 | """
486 | 2013/07/08 23:51:21 Error: No such container: #{keyword}
487 | """
488 |
489 | commit_containerid = \
490 | """
491 | effb66b31edb
492 | """
493 |
494 | help = \
495 | "
496 | Docker tutorial \n
497 | \n
498 | The Docker tutorial is a Docker emulater intended to help novice users get up to spead with the standard Docker
499 | commands. This terminal contains a limited Docker and a limited shell emulator. Therefore some of the commands
500 | you would expect do not exist.\n
501 | \n
502 | Just follow the steps and questions. If you are stuck, click on the 'expected command' to see what the command
503 | should have been. Leave feedback if you find things confusing.
504 |
505 | "
506 |
507 | images = \
508 | """
509 | ubuntu latest 8dbd9e392a96 4 months ago 131.5 MB (virtual 131.5 MB)
510 | learn/tutorial latest 8dbd9e392a96 2 months ago 131.5 MB (virtual 131.5 MB)
511 | learn/ping latest effb66b31edb 10 minutes ago 11.57 MB (virtual 143.1 MB)
512 | """
513 |
514 | inspect = \
515 | """
516 |
517 | Usage: Docker inspect CONTAINER|IMAGE [CONTAINER|IMAGE...]
518 |
519 | Return low-level information on a container/image
520 |
521 | """
522 |
523 | inspect_no_such_container = (keyword) ->
524 | """
525 | Error: No such image: #{keyword}
526 | """
527 |
528 | inspect_ping_container = \
529 | """
530 | [2013/07/30 01:52:26 GET /v1.3/containers/efef/json
531 | {
532 | "ID": "efefdc74a1d5900d7d7a74740e5261c09f5f42b6dae58ded6a1fde1cde7f4ac5",
533 | "Created": "2013-07-30T00:54:12.417119736Z",
534 | "Path": "ping",
535 | "Args": [
536 | "www.google.com"
537 | ],
538 | "Config": {
539 | "Hostname": "efefdc74a1d5",
540 | "User": "",
541 | "Memory": 0,
542 | "MemorySwap": 0,
543 | "CpuShares": 0,
544 | "AttachStdin": false,
545 | "AttachStdout": true,
546 | "AttachStderr": true,
547 | "PortSpecs": null,
548 | "Tty": false,
549 | "OpenStdin": false,
550 | "StdinOnce": false,
551 | "Env": null,
552 | "Cmd": [
553 | "ping",
554 | "www.google.com"
555 | ],
556 | "Dns": null,
557 | "Image": "learn/ping",
558 | "Volumes": null,
559 | "VolumesFrom": "",
560 | "Entrypoint": null
561 | },
562 | "State": {
563 | "Running": true,
564 | "Pid": 22249,
565 | "ExitCode": 0,
566 | "StartedAt": "2013-07-30T00:54:12.424817715Z",
567 | "Ghost": false
568 | },
569 | "Image": "a1dbb48ce764c6651f5af98b46ed052a5f751233d731b645a6c57f91a4cb7158",
570 | "NetworkSettings": {
571 | "IPAddress": "172.16.42.6",
572 | "IPPrefixLen": 24,
573 | "Gateway": "172.16.42.1",
574 | "Bridge": "docker0",
575 | "PortMapping": {
576 | "Tcp": {},
577 | "Udp": {}
578 | }
579 | },
580 | "SysInitPath": "/usr/bin/docker",
581 | "ResolvConfPath": "/etc/resolv.conf",
582 | "Volumes": {},
583 | "VolumesRW": {}
584 | """
585 |
586 | ping = \
587 | """
588 | Usage: ping [-LRUbdfnqrvVaAD] [-c count] [-i interval] [-w deadline]
589 | [-p pattern] [-s packetsize] [-t ttl] [-I interface]
590 | [-M pmtudisc-hint] [-m mark] [-S sndbuf]
591 | [-T tstamp-options] [-Q tos] [hop1 ...] destination
592 | """
593 |
594 | ps = \
595 | """
596 | ID IMAGE COMMAND CREATED STATUS PORTS
597 | efefdc74a1d5 learn/ping:latest ping www.google.com 37 seconds ago Up 36 seconds
598 | """
599 |
600 | ps_a = \
601 | """
602 | ID IMAGE COMMAND CREATED STATUS PORTS
603 | 6982a9948422 ubuntu:12.04 apt-get install ping 1 minute ago Exit 0
604 | efefdc74a1d5 learn/ping:latest ping www.google.com 37 seconds ago Up 36 seconds
605 | """
606 |
607 | ps_l = \
608 | """
609 | ID IMAGE COMMAND CREATED STATUS PORTS
610 | 6982a9948422 ubuntu:12.04 apt-get install ping 1 minute ago Exit 0
611 | """
612 |
613 | pull = \
614 | """
615 | Usage: Docker pull NAME
616 |
617 | Pull an image or a repository from the registry
618 |
619 | -registry="": Registry to download from. Necessary if image is pulled by ID
620 | -t="": Download tagged image in repository
621 | """
622 |
623 | pull_no_results = (keyword) ->
624 | """
625 | Pulling repository #{keyword}
626 | 2013/06/19 19:27:03 HTTP code: 404
627 | """
628 |
629 | pull_ubuntu =
630 | """
631 | Pulling repository ubuntu from https://index.docker.io/v1
632 | Pulling image 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c (precise) from ubuntu
633 | Pulling image b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc (12.10) from ubuntu
634 | Pulling image 27cf784147099545 () from ubuntu
635 | """
636 |
637 | pull_tutorial = \
638 | """
639 | Pulling repository learn/tutorial from https://index.docker.io/v1
640 | Pulling image 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c (precise) from ubuntu
641 | Pulling image b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc (12.10) from ubuntu
642 | Pulling image 27cf784147099545 () from tutorial
643 | """
644 |
645 | push = \
646 | """
647 |
648 | Usage: docker push NAME
649 |
650 | Push an image or a repository to the registry
651 | """
652 |
653 |
654 | push_container_learn_ping = \
655 | """
656 | The push refers to a repository [learn/ping] (len: 1)
657 | Processing checksums
658 | Sending image list
659 | Pushing repository learn/ping (1 tags)
660 | Pushing 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c
661 | Image 8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c already pushed, skipping
662 | Pushing tags for rev [8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c] on {https://registry-1.docker.io/v1/repositories/learn/ping/tags/latest}
663 | Pushing a1dbb48ce764c6651f5af98b46ed052a5f751233d731b645a6c57f91a4cb7158
664 | Pushing 11.5 MB/11.5 MB (100%)
665 | Pushing tags for rev [a1dbb48ce764c6651f5af98b46ed052a5f751233d731b645a6c57f91a4cb7158] on {https://registry-1.docker.io/v1/repositories/learn/ping/tags/latest}
666 | """
667 |
668 | push_wrong_name = \
669 | """
670 | The push refers to a repository [dhrp/fail] (len: 0)
671 | """
672 |
673 | run_cmd = \
674 | """
675 | Usage: Docker run [OPTIONS] IMAGE COMMAND [ARG...]
676 |
677 | Run a command in a new container
678 |
679 | -a=map[]: Attach to stdin, stdout or stderr.
680 | -c=0: CPU shares (relative weight)
681 | -d=false: Detached mode: leave the container running in the background
682 | -dns=[]: Set custom dns servers
683 | -e=[]: Set environment variables
684 | -h="": Container host name
685 | -i=false: Keep stdin open even if not attached
686 | -m=0: Memory limit (in bytes)
687 | -p=[]: Expose a container's port to the host (use 'docker port' to see the actual mapping)
688 | -t=false: Allocate a pseudo-tty
689 | -u="": Username or UID
690 | -v=map[]: Attach a data volume
691 | -volumes-from="": Mount volumes from the specified container
692 | """
693 |
694 | run_apt_get = \
695 | """
696 | apt 0.8.16~exp12ubuntu10 for amd64 compiled on Apr 20 2012 10:19:39
697 | Usage: apt-get [options] command
698 | apt-get [options] install|remove pkg1 [pkg2 ...]
699 | apt-get [options] source pkg1 [pkg2 ...]
700 |
701 | apt-get is a simple command line interface for downloading and
702 | installing packages. The most frequently used commands are update
703 | and install.
704 |
705 | Commands:
706 | update - Retrieve new lists of packages
707 | upgrade - Perform an upgrade
708 | install - Install new packages (pkg is libc6 not libc6.deb)
709 | remove - Remove packages
710 | autoremove - Remove automatically all unused packages
711 | purge - Remove packages and config files
712 | source - Download source archives
713 | build-dep - Configure build-dependencies for source packages
714 | dist-upgrade - Distribution upgrade, see apt-get(8)
715 | dselect-upgrade - Follow dselect selections
716 | clean - Erase downloaded archive files
717 | autoclean - Erase old downloaded archive files
718 | check - Verify that there are no broken dependencies
719 | changelog - Download and display the changelog for the given package
720 | download - Download the binary package into the current directory
721 |
722 | Options:
723 | -h This help text.
724 | -q Loggable output - no progress indicator
725 | -qq No output except for errors
726 | -d Download only - do NOT install or unpack archives
727 | -s No-act. Perform ordering simulation
728 | -y Assume Yes to all queries and do not prompt
729 | -f Attempt to correct a system with broken dependencies in place
730 | -m Attempt to continue if archives are unlocatable
731 | -u Show a list of upgraded packages as well
732 | -b Build the source package after fetching it
733 | -V Show verbose version numbers
734 | -c=? Read this configuration file
735 | -o=? Set an arbitrary configuration option, eg -o dir::cache=/tmp
736 | See the apt-get(8), sources.list(5) and apt.conf(5) manual
737 | pages for more information and options.
738 | This APT has Super Cow Powers.
739 |
740 | """
741 |
742 | run_apt_get_install_iputils_ping = \
743 | """
744 | Reading package lists...
745 | Building dependency tree...
746 | The following NEW packages will be installed:
747 | iputils-ping
748 | 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
749 | Need to get 56.1 kB of archives.
750 | After this operation, 143 kB of additional disk space will be used.
751 | Get:1 http://archive.ubuntu.com/ubuntu/ precise/main iputils-ping amd64 3:20101006-1ubuntu1 [56.1 kB]
752 | debconf: delaying package configuration, since apt-utils is not installed
753 | Fetched 56.1 kB in 1s (50.3 kB/s)
754 | Selecting previously unselected package iputils-ping.
755 | (Reading database ... 7545 files and directories currently installed.)
756 | Unpacking iputils-ping (from .../iputils-ping_3%3a20101006-1ubuntu1_amd64.deb) ...
757 | Setting up iputils-ping (3:20101006-1ubuntu1) ...
758 | """
759 |
760 | run_apt_get_install_unknown_package = (keyword) ->
761 | """
762 | Reading package lists...
763 | Building dependency tree...
764 | E: Unable to locate package #{keyword}
765 | """
766 |
767 | run_flag_defined_not_defined = (keyword) ->
768 | """
769 | 2013/08/15 22:19:14 flag provided but not defined: #{keyword}
770 | """
771 |
772 | run_learn_no_command = \
773 | """
774 | 2013/07/02 02:00:59 Error: No command specified
775 | """
776 |
777 | run_learn_tutorial_echo_hello_world = (commands) ->
778 | string = ""
779 | for command in commands[1..]
780 | command = command.replace('"','');
781 | string += ("#{command} ")
782 | return string
783 |
784 |
785 | run_image_wrong_command = (keyword) ->
786 | """
787 | 2013/07/08 23:13:30 Unable to locate #{keyword}
788 | """
789 |
790 | run_notfound = (keyword) ->
791 | """
792 | Pulling repository #{keyword} from https://index.docker.io/v1
793 | 2013/07/02 02:14:47 Error: No such image: #{keyword}
794 | """
795 |
796 | run_ping_not_google = (keyword) ->
797 | """
798 | ping: unknown host #{keyword}
799 | """
800 |
801 | run_ping_www_google_com = \
802 | """
803 | PING www.google.com (74.125.239.129) 56(84) bytes of data.
804 | 64 bytes from nuq05s02-in-f20.1e100.net (74.125.239.148): icmp_req=1 ttl=55 time=2.23 ms
805 | 64 bytes from nuq05s02-in-f20.1e100.net (74.125.239.148): icmp_req=2 ttl=55 time=2.30 ms
806 | 64 bytes from nuq05s02-in-f20.1e100.net (74.125.239.148): icmp_req=3 ttl=55 time=2.27 ms
807 | 64 bytes from nuq05s02-in-f20.1e100.net (74.125.239.148): icmp_req=4 ttl=55 time=2.30 ms
808 | 64 bytes from nuq05s02-in-f20.1e100.net (74.125.239.148): icmp_req=5 ttl=55 time=2.25 ms
809 | 64 bytes from nuq05s02-in-f20.1e100.net (74.125.239.148): icmp_req=6 ttl=55 time=2.29 ms
810 | 64 bytes from nuq05s02-in-f20.1e100.net (74.125.239.148): icmp_req=7 ttl=55 time=2.23 ms
811 | 64 bytes from nuq05s02-in-f20.1e100.net (74.125.239.148): icmp_req=8 ttl=55 time=2.30 ms
812 | 64 bytes from nuq05s02-in-f20.1e100.net (74.125.239.148): icmp_req=9 ttl=55 time=2.35 ms
813 | -> This would normally just keep going. However, this emulator does not support Ctrl-C, so we quit here.
814 | """
815 |
816 | search = \
817 | """
818 |
819 | Usage: Docker search NAME
820 |
821 | Search the Docker index for images
822 |
823 | """
824 |
825 | search_no_results = (keyword) ->
826 | """
827 | Found 0 results matching your query ("#{keyword}")
828 | NAME DESCRIPTION
829 | """
830 |
831 | search_tutorial = \
832 | """
833 | Found 1 results matching your query ("tutorial")
834 | NAME DESCRIPTION
835 | learn/tutorial An image for the interactive tutorial
836 | """
837 |
838 | search_ubuntu = \
839 | """
840 | Found 22 results matching your query ("ubuntu")
841 | NAME DESCRIPTION
842 | shykes/ubuntu
843 | base Another general use Ubuntu base image. Tag...
844 | ubuntu General use Ubuntu base image. Tags availa...
845 | boxcar/raring Ubuntu Raring 13.04 suitable for testing v...
846 | dhrp/ubuntu
847 | creack/ubuntu Tags:
848 | 12.04-ssh,
849 | 12.10-ssh,
850 | 12.10-ssh-l...
851 | crohr/ubuntu Ubuntu base images. Only lucid (10.04) for...
852 | knewton/ubuntu
853 | pallet/ubuntu2
854 | erikh/ubuntu
855 | samalba/wget Test container inherited from ubuntu with ...
856 | creack/ubuntu-12-10-ssh
857 | knewton/ubuntu-12.04
858 | tithonium/rvm-ubuntu The base 'ubuntu' image, with rvm installe...
859 | dekz/build 13.04 ubuntu with build
860 | ooyala/test-ubuntu
861 | ooyala/test-my-ubuntu
862 | ooyala/test-ubuntu2
863 | ooyala/test-ubuntu3
864 | ooyala/test-ubuntu4
865 | ooyala/test-ubuntu5
866 | surma/go Simple augmentation of the standard Ubuntu...
867 |
868 | """
869 |
870 | testing = \
871 | """
872 | Testing leads to failure, and failure leads to understanding. ~Burt Rutan
873 | """
874 |
875 | docker_version = () ->
876 | """
877 | Docker Emulator version #{EMULATOR_VERSION}
878 |
879 | Emulating:
880 | Client version: 0.5.3
881 | Server version: 0.5.3
882 | Go version: go1.1
883 | """
884 |
885 |
886 | Docker_logo = \
887 | '''
888 | _ _ _ _
889 | __ _____| | | __| | ___ _ __ ___ | |
890 | \\\ \\\ /\\\ / / _ \\\ | | / _` |/ _ \\\| '_ \\\ / _ \\\ | |
891 | \\\ V V / __/ | | | (_| | (_) | | | | __/ |_|
892 | \\\_/\\\_/ \\\___|_|_| \\\__,_|\\\___/|_| |_|\\\___| (_)
893 |
894 |
895 |
896 |
897 | ## .
898 | ## ## ## ==
899 | ## ## ## ## ===
900 | /""""""""""""""""\\\___/ ===
901 | ~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
902 | \\\______ o __/
903 | \\\ \\\ __/
904 | \\\____\\\______/
905 |
906 | | |
907 | __ | __ __ | _ __ _
908 | / \\\| / \\\ / |/ / _\\\ |
909 | \\\__/| \\\__/ \\\__ |\\\_ \\\__ |
910 |
911 |
912 | '''
913 |
914 |
915 | return this
916 |
917 |
918 |
--------------------------------------------------------------------------------