Merge branch 'jonas_master' into refactor_test_1
This commit is contained in:
15
Dockerfile
15
Dockerfile
@@ -12,8 +12,15 @@ RUN go get -v -d ./...
|
|||||||
|
|
||||||
RUN CGO_ENABLED=0 go build -a -installsuffix nocgo -o /go/bin/play-with-docker .
|
RUN CGO_ENABLED=0 go build -a -installsuffix nocgo -o /go/bin/play-with-docker .
|
||||||
|
|
||||||
# Set the workdir to be /go/bin which is where the binaries are built
|
FROM alpine
|
||||||
WORKDIR /go/bin
|
|
||||||
|
|
||||||
# Export the WORKDIR as a tar stream
|
RUN apk --update add ca-certificates
|
||||||
CMD tar -cf - .
|
RUN mkdir -p /app/pwd
|
||||||
|
|
||||||
|
COPY --from=0 /go/bin/play-with-docker /app/play-with-docker
|
||||||
|
COPY ./www /app/www
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
CMD ["./play-with-docker"]
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
ARG VERSION=17.05.0-ce-dind
|
ARG VERSION=docker:17.05.0-ce-dind
|
||||||
FROM docker:${VERSION}
|
FROM ${VERSION}
|
||||||
|
|
||||||
RUN apk add --no-cache git tmux py2-pip apache2-utils vim build-base gettext-dev curl bash-completion bash util-linux jq
|
RUN apk add --no-cache git tmux py2-pip apache2-utils vim build-base gettext-dev curl bash-completion bash util-linux jq
|
||||||
|
|
||||||
ENV COMPOSE_VERSION=1.12.0
|
|
||||||
ENV MACHINE_VERSION=v0.10.0
|
|
||||||
# Install Compose and Machine
|
|
||||||
RUN pip install docker-compose==${COMPOSE_VERSION}
|
|
||||||
RUN curl -L https://github.com/docker/machine/releases/download/${MACHINE_VERSION}/docker-machine-Linux-x86_64 \
|
|
||||||
-o /usr/bin/docker-machine && chmod +x /usr/bin/docker-machine
|
|
||||||
|
|
||||||
# Compile and install httping
|
# Compile and install httping
|
||||||
# (used in orchestration workshop, and very useful anyway)
|
# (used in orchestration workshop, and very useful anyway)
|
||||||
@@ -18,6 +12,13 @@ RUN mkdir -p /opt && cd /opt && \
|
|||||||
./configure && make install LDFLAGS=-lintl && \
|
./configure && make install LDFLAGS=-lintl && \
|
||||||
rm -rf httping-2.5
|
rm -rf httping-2.5
|
||||||
|
|
||||||
|
ENV COMPOSE_VERSION=1.13.0
|
||||||
|
ENV MACHINE_VERSION=v0.11.0
|
||||||
|
# Install Compose and Machine
|
||||||
|
RUN pip install docker-compose==${COMPOSE_VERSION}
|
||||||
|
RUN curl -L https://github.com/docker/machine/releases/download/${MACHINE_VERSION}/docker-machine-Linux-x86_64 \
|
||||||
|
-o /usr/bin/docker-machine && chmod +x /usr/bin/docker-machine
|
||||||
|
|
||||||
# Add bash completion
|
# Add bash completion
|
||||||
RUN mkdir /etc/bash_completion.d && curl https://raw.githubusercontent.com/docker/docker/master/contrib/completion/bash/docker -o /etc/bash_completion.d/docker
|
RUN mkdir /etc/bash_completion.d && curl https://raw.githubusercontent.com/docker/docker/master/contrib/completion/bash/docker -o /etc/bash_completion.d/docker
|
||||||
|
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -22,7 +22,7 @@ the daemon won't load it automatically. Run the following command for that purpo
|
|||||||
|
|
||||||
Start the Docker daemon on your machine and run `docker pull franela/dind`.
|
Start the Docker daemon on your machine and run `docker pull franela/dind`.
|
||||||
|
|
||||||
1) Install go 1.7.1 with `brew` on Mac or through a package manager.
|
1) Install go 1.7.1+ with `brew` on Mac or through a package manager.
|
||||||
|
|
||||||
2) `go get -v -d -t ./...`
|
2) `go get -v -d -t ./...`
|
||||||
|
|
||||||
@@ -35,13 +35,27 @@ Notes:
|
|||||||
* There is a hard-coded limit to 5 Docker playgrounds per session. After 4 hours sessions are deleted.
|
* There is a hard-coded limit to 5 Docker playgrounds per session. After 4 hours sessions are deleted.
|
||||||
* If you want to override the DIND version or image then set the environmental variable i.e.
|
* If you want to override the DIND version or image then set the environmental variable i.e.
|
||||||
`DIND_IMAGE=franela/docker<version>-rc:dind`. Take into account that you can't use standard `dind` images, only [franela](https://hub.docker.com/r/franela/) ones work.
|
`DIND_IMAGE=franela/docker<version>-rc:dind`. Take into account that you can't use standard `dind` images, only [franela](https://hub.docker.com/r/franela/) ones work.
|
||||||
|
|
||||||
|
### Port forwarding
|
||||||
|
|
||||||
|
In order for port forwarding to work correctly in development you need to make `*.localhost` to resolve to `127.0.0.1`. That way when you try to access to `pwd10-0-0-1-8080.host1.localhost`, then you're forwarded correctly to your local PWD server.
|
||||||
|
|
||||||
|
You can achieve this by setting up a `dnsmasq` server (you can run it in a docker container also) and adding the following configuration:
|
||||||
|
|
||||||
|
```
|
||||||
|
address=/localhost/127.0.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Don't forget to change your computer default DNS to use the dnsmasq server to resolve.
|
||||||
|
|
||||||
|
### Building the dind image myself.
|
||||||
|
|
||||||
|
If you want to make changes to the `dind` image being used, make your changes to the `Dockerfile.dind` file and then build it using this command: `docker build --build-arg docker_storage_driver=vfs -f Dockerfile.dind -t franela/dind .`
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### How can I connect to a published port from the outside world?
|
### How can I connect to a published port from the outside world?
|
||||||
|
|
||||||
~~We're planning to setup a reverse proxy that handles redirection automatically, in the meantime you can use [ngrok](https://ngrok.com) within PWD running `docker run --name supergrok -d jpetazzo/supergrok` then `docker logs --follow supergrok` , it will give you a ngrok URL, now you can go to that URL and add the IP+port that you want to connect to… e.g. if your PWD instance is 10.0.42.3, you can go to http://xxxxxx.ngrok.io/10.0.42.3:8000 (where the xxxxxx is given to you in the supergrok logs).~~
|
|
||||||
|
|
||||||
If you need to access your services from outside, use the following URL pattern `http://pwd<underscore_ip>-<port>.<host#>.labs.play-with-docker.com` (i.e: http://pwd10_2_135_3-80.host3.labs.play-with-docker.com/).
|
If you need to access your services from outside, use the following URL pattern `http://pwd<underscore_ip>-<port>.<host#>.labs.play-with-docker.com` (i.e: http://pwd10_2_135_3-80.host3.labs.play-with-docker.com/).
|
||||||
|
|
||||||
|
|||||||
3
build.sh
3
build.sh
@@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
docker build -t builder .&& docker run --rm builder | docker build -t franela/play-with-docker:latest -
|
|
||||||
@@ -11,8 +11,14 @@ func Home(w http.ResponseWriter, r *http.Request) {
|
|||||||
sessionId := vars["sessionId"]
|
sessionId := vars["sessionId"]
|
||||||
|
|
||||||
s := core.SessionGet(sessionId)
|
s := core.SessionGet(sessionId)
|
||||||
|
if s == nil {
|
||||||
|
// Session doesn't exist (can happen if closing the sessions an reloading the page, or similar).
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
if s.Stack != "" {
|
if s.Stack != "" {
|
||||||
go core.SessionDeployStack(s)
|
go core.SessionDeployStack(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
http.ServeFile(w, r, "./www/index.html")
|
http.ServeFile(w, r, "./www/index.html")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/play-with-docker/play-with-docker/config"
|
"github.com/play-with-docker/play-with-docker/config"
|
||||||
"github.com/play-with-docker/play-with-docker/recaptcha"
|
"github.com/play-with-docker/play-with-docker/recaptcha"
|
||||||
@@ -25,8 +27,10 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
reqDur := req.Form.Get("session-duration")
|
reqDur := req.Form.Get("session-duration")
|
||||||
stack := req.Form.Get("stack")
|
stack := req.Form.Get("stack")
|
||||||
|
stackName := req.Form.Get("stack_name")
|
||||||
|
|
||||||
if stack != "" {
|
if stack != "" {
|
||||||
|
stack = formatStack(stack)
|
||||||
if ok, err := stackExists(stack); err != nil {
|
if ok, err := stackExists(stack); err != nil {
|
||||||
log.Printf("Error retrieving stack: %s", err)
|
log.Printf("Error retrieving stack: %s", err)
|
||||||
rw.WriteHeader(http.StatusInternalServerError)
|
rw.WriteHeader(http.StatusInternalServerError)
|
||||||
@@ -39,7 +43,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
duration := config.GetDuration(reqDur)
|
duration := config.GetDuration(reqDur)
|
||||||
s, err := core.SessionNew(duration, stack, "")
|
s, err := core.SessionNew(duration, stack, stackName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
//TODO: Return some error code
|
//TODO: Return some error code
|
||||||
@@ -61,13 +65,24 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatStack(stack string) string {
|
||||||
|
if !strings.HasSuffix(stack, ".yml") {
|
||||||
|
// If it doesn't end with ".yml", assume it hasn't been specified, then default to "stack.yml"
|
||||||
|
stack = path.Join(stack, "stack.yml")
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(stack, "/") {
|
||||||
|
// The host is anonymous, then use our own stack repo.
|
||||||
|
stack = fmt.Sprintf("%s%s", "https://raw.githubusercontent.com/play-with-docker/stacks/master", stack)
|
||||||
|
}
|
||||||
|
return stack
|
||||||
|
}
|
||||||
|
|
||||||
func stackExists(stack string) (bool, error) {
|
func stackExists(stack string) (bool, error) {
|
||||||
resp, err := http.Head("https://raw.githubusercontent.com/play-with-docker/stacks/master" + stack)
|
resp, err := http.Head(stack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
return resp.StatusCode == 200, nil
|
return resp.StatusCode == 200, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,11 +47,14 @@ func (p *pwd) SessionNew(duration time.Duration, stack, stackName string) (*Sess
|
|||||||
s.ExpiresAt = s.CreatedAt.Add(duration)
|
s.ExpiresAt = s.CreatedAt.Add(duration)
|
||||||
s.Ready = true
|
s.Ready = true
|
||||||
s.Stack = stack
|
s.Stack = stack
|
||||||
s.StackName = stackName
|
|
||||||
|
|
||||||
if s.Stack != "" {
|
if s.Stack != "" {
|
||||||
s.Ready = false
|
s.Ready = false
|
||||||
}
|
}
|
||||||
|
if stackName == "" {
|
||||||
|
stackName = "pwd"
|
||||||
|
}
|
||||||
|
s.StackName = stackName
|
||||||
|
|
||||||
log.Printf("NewSession id=[%s]\n", s.Id)
|
log.Printf("NewSession id=[%s]\n", s.Id)
|
||||||
|
|
||||||
@@ -142,32 +145,32 @@ func (p *pwd) SessionDeployStack(s *Session) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.Ready = false
|
s.Ready = false
|
||||||
p.broadcast.BroadcastTo(s.Id, "session ready", s.Ready)
|
p.broadcast.BroadcastTo(s.Id, "session ready", false)
|
||||||
|
|
||||||
i, err := p.InstanceNew(s, InstanceConfig{})
|
i, err := p.InstanceNew(s, InstanceConfig{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error creating instance for stack [%s]: %s\n", s.Stack, err)
|
log.Printf("Error creating instance for stack [%s]: %s\n", s.Stack, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = p.InstanceUploadFromUrl(i, "https://raw.githubusercontent.com/play-with-docker/stacks/master"+s.Stack)
|
err = p.InstanceUploadFromUrl(i, s.Stack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error uploading stack file [%s]: %s\n", s.Stack, err)
|
log.Printf("Error uploading stack file [%s]: %s\n", s.Stack, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w := sessionBuilderWriter{sessionId: s.Id, broadcast: p.broadcast}
|
|
||||||
fileName := path.Base(s.Stack)
|
fileName := path.Base(s.Stack)
|
||||||
code, err := p.docker.ExecAttach(i.Name, []string{"docker-compose", "-f", "/var/run/pwd/uploads/" + fileName, "up", "-d"}, &w)
|
file := fmt.Sprintf("/var/run/pwd/uploads/%s", fileName)
|
||||||
|
cmd := fmt.Sprintf("docker swarm init --advertise-addr eth0 && docker-compose -f %s pull && docker stack deploy -c %s %s", file, file, s.StackName)
|
||||||
|
|
||||||
|
w := sessionBuilderWriter{sessionId: s.Id, broadcast: p.broadcast}
|
||||||
|
code, err := p.docker.ExecAttach(i.Name, []string{"sh", "-c", cmd}, &w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error executing stack [%s]: %s\n", s.Stack, err)
|
log.Printf("Error executing stack [%s]: %s\n", s.Stack, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Stack execution finished with code %d\n", code)
|
log.Printf("Stack execution finished with code %d\n", code)
|
||||||
|
|
||||||
s.Ready = true
|
s.Ready = true
|
||||||
p.broadcast.BroadcastTo(s.Id, "session ready", s.Ready)
|
p.broadcast.BroadcastTo(s.Id, "session ready", true)
|
||||||
|
|
||||||
if err := p.storage.Save(); err != nil {
|
if err := p.storage.Save(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -176,16 +179,6 @@ func (p *pwd) SessionDeployStack(s *Session) error {
|
|||||||
|
|
||||||
func (p *pwd) SessionGet(sessionId string) *Session {
|
func (p *pwd) SessionGet(sessionId string) *Session {
|
||||||
s := sessions[sessionId]
|
s := sessions[sessionId]
|
||||||
/*
|
|
||||||
if s != nil {
|
|
||||||
for _, instance := range s.Instances {
|
|
||||||
if !instance.IsConnected() {
|
|
||||||
instance.SetSession(s)
|
|
||||||
go instance.Attach()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}*/
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ func TestSessionNew(t *testing.T) {
|
|||||||
assert.Nil(t, e)
|
assert.Nil(t, e)
|
||||||
assert.NotNil(t, s)
|
assert.NotNil(t, s)
|
||||||
|
|
||||||
|
assert.Equal(t, "pwd", s.StackName)
|
||||||
|
|
||||||
assert.NotEmpty(t, s.Id)
|
assert.NotEmpty(t, s.Id)
|
||||||
assert.WithinDuration(t, s.CreatedAt, before, time.Since(before))
|
assert.WithinDuration(t, s.CreatedAt, before, time.Since(before))
|
||||||
assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second)
|
assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second)
|
||||||
|
|||||||
@@ -63,6 +63,8 @@
|
|||||||
KeyboardShortcutService.setResizeFunc($scope.resize);
|
KeyboardShortcutService.setResizeFunc($scope.resize);
|
||||||
|
|
||||||
$scope.closeSession = function() {
|
$scope.closeSession = function() {
|
||||||
|
// Remove alert before closing browser tab
|
||||||
|
window.onbeforeunload = null;
|
||||||
$scope.socket.emit('session close');
|
$scope.socket.emit('session close');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
@import url('https://fonts.googleapis.com/css?family=Rationale');
|
@import url('https://fonts.googleapis.com/css?family=Rationale');
|
||||||
|
|
||||||
.selected button {
|
.selected button {
|
||||||
background-color: rgba(158,158,158,0.2);
|
background-color: rgba(158,158,158,0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
md-card-content.terminal-container {
|
md-card-content.terminal-container {
|
||||||
@@ -49,3 +49,13 @@ md-input-container .md-errors-spacer {
|
|||||||
.stats {
|
.stats {
|
||||||
min-height: 230px;
|
min-height: 230px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 7px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: rgba(0,0,0,.5);
|
||||||
|
-webkit-box-shadow: 0 0 1px rgba(255,255,255,.5);
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<form id="welcomeFormBypass" method="POST" action="/">
|
<form id="welcomeFormBypass" method="POST" action="/">
|
||||||
<button id="start" type="submit">Start Session</button>
|
<button id="start" type="submit">Start Session</button>
|
||||||
<input id="stack" type="hidden" name="stack" value=""/>
|
<input id="stack" type="hidden" name="stack" value=""/>
|
||||||
|
<input id="stack_name" type="hidden" name="stack_name" value=""/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
@@ -37,5 +38,9 @@
|
|||||||
if (stack) {
|
if (stack) {
|
||||||
document.getElementById('stack').value = stack;
|
document.getElementById('stack').value = stack;
|
||||||
}
|
}
|
||||||
|
var stackName = getParameterByName('stack_name');
|
||||||
|
if (stack) {
|
||||||
|
document.getElementById('stack_name').value = stackName;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
<div id="recaptcha" class="g-recaptcha" data-callback="iAmHuman" data-sitekey="{{.}}"></div>
|
<div id="recaptcha" class="g-recaptcha" data-callback="iAmHuman" data-sitekey="{{.}}"></div>
|
||||||
<input type="hidden" name="session-duration" value="4h"/>
|
<input type="hidden" name="session-duration" value="4h"/>
|
||||||
<input id="stack" type="hidden" name="stack" value=""/>
|
<input id="stack" type="hidden" name="stack" value=""/>
|
||||||
|
<input id="stack_name" type="hidden" name="stack_name" value=""/>
|
||||||
<button id="create" style="display:none;">Create session</button>
|
<button id="create" style="display:none;">Create session</button>
|
||||||
</form>
|
</form>
|
||||||
<img src="/assets/large_h.png" />
|
<img src="/assets/large_h.png" />
|
||||||
@@ -38,6 +39,10 @@
|
|||||||
if (stack) {
|
if (stack) {
|
||||||
document.getElementById('stack').value = stack;
|
document.getElementById('stack').value = stack;
|
||||||
}
|
}
|
||||||
|
var stackName = getParameterByName('stack_name');
|
||||||
|
if (stackName) {
|
||||||
|
document.getElementById('stack_name').value = stackName;
|
||||||
|
}
|
||||||
if (document.cookie.indexOf('session_id') > -1) {
|
if (document.cookie.indexOf('session_id') > -1) {
|
||||||
document.getElementById('create').style = "";
|
document.getElementById('create').style = "";
|
||||||
document.getElementById('recaptcha').style = "display:none;";
|
document.getElementById('recaptcha').style = "display:none;";
|
||||||
|
|||||||
Reference in New Issue
Block a user