Merge branch 'jonas_master' into refactor_test_1

This commit is contained in:
Jonathan Leibiusky @xetorthio
2017-05-24 10:00:03 -03:00
12 changed files with 97 additions and 40 deletions

View File

@@ -12,8 +12,15 @@ RUN go get -v -d ./...
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
WORKDIR /go/bin
FROM alpine
# Export the WORKDIR as a tar stream
CMD tar -cf - .
RUN apk --update add ca-certificates
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

View File

@@ -1,14 +1,8 @@
ARG VERSION=17.05.0-ce-dind
FROM docker:${VERSION}
ARG VERSION=docker:17.05.0-ce-dind
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
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
# (used in orchestration workshop, and very useful anyway)
@@ -18,6 +12,13 @@ RUN mkdir -p /opt && cd /opt && \
./configure && make install LDFLAGS=-lintl && \
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
RUN mkdir /etc/bash_completion.d && curl https://raw.githubusercontent.com/docker/docker/master/contrib/completion/bash/docker -o /etc/bash_completion.d/docker

View File

@@ -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`.
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 ./...`
@@ -35,13 +35,27 @@ Notes:
* 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.
`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
### 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/).

View File

@@ -1,3 +0,0 @@
#!/bin/bash
docker build -t builder .&& docker run --rm builder | docker build -t franela/play-with-docker:latest -

View File

@@ -11,8 +11,14 @@ func Home(w http.ResponseWriter, r *http.Request) {
sessionId := vars["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 != "" {
go core.SessionDeployStack(s)
}
http.ServeFile(w, r, "./www/index.html")
}

View File

@@ -5,6 +5,8 @@ import (
"fmt"
"log"
"net/http"
"path"
"strings"
"github.com/play-with-docker/play-with-docker/config"
"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")
stack := req.Form.Get("stack")
stackName := req.Form.Get("stack_name")
if stack != "" {
stack = formatStack(stack)
if ok, err := stackExists(stack); err != nil {
log.Printf("Error retrieving stack: %s", err)
rw.WriteHeader(http.StatusInternalServerError)
@@ -39,7 +43,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
}
duration := config.GetDuration(reqDur)
s, err := core.SessionNew(duration, stack, "")
s, err := core.SessionNew(duration, stack, stackName)
if err != nil {
log.Println(err)
//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) {
resp, err := http.Head("https://raw.githubusercontent.com/play-with-docker/stacks/master" + stack)
resp, err := http.Head(stack)
if err != nil {
return false, err
}
defer resp.Body.Close()
return resp.StatusCode == 200, nil
}

View File

@@ -47,11 +47,14 @@ func (p *pwd) SessionNew(duration time.Duration, stack, stackName string) (*Sess
s.ExpiresAt = s.CreatedAt.Add(duration)
s.Ready = true
s.Stack = stack
s.StackName = stackName
if s.Stack != "" {
s.Ready = false
}
if stackName == "" {
stackName = "pwd"
}
s.StackName = stackName
log.Printf("NewSession id=[%s]\n", s.Id)
@@ -142,32 +145,32 @@ func (p *pwd) SessionDeployStack(s *Session) error {
}
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{})
if err != nil {
log.Printf("Error creating instance for stack [%s]: %s\n", s.Stack, 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 {
log.Printf("Error uploading stack file [%s]: %s\n", s.Stack, err)
return err
}
w := sessionBuilderWriter{sessionId: s.Id, broadcast: p.broadcast}
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 {
log.Printf("Error executing stack [%s]: %s\n", s.Stack, err)
return err
}
log.Printf("Stack execution finished with code %d\n", code)
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 {
return err
}
@@ -176,16 +179,6 @@ func (p *pwd) SessionDeployStack(s *Session) error {
func (p *pwd) SessionGet(sessionId string) *Session {
s := sessions[sessionId]
/*
if s != nil {
for _, instance := range s.Instances {
if !instance.IsConnected() {
instance.SetSession(s)
go instance.Attach()
}
}
}*/
return s
}

View File

@@ -43,6 +43,8 @@ func TestSessionNew(t *testing.T) {
assert.Nil(t, e)
assert.NotNil(t, s)
assert.Equal(t, "pwd", s.StackName)
assert.NotEmpty(t, s.Id)
assert.WithinDuration(t, s.CreatedAt, before, time.Since(before))
assert.WithinDuration(t, s.ExpiresAt, before.Add(time.Hour), time.Second)

View File

@@ -63,6 +63,8 @@
KeyboardShortcutService.setResizeFunc($scope.resize);
$scope.closeSession = function() {
// Remove alert before closing browser tab
window.onbeforeunload = null;
$scope.socket.emit('session close');
}

View File

@@ -1,7 +1,7 @@
@import url('https://fonts.googleapis.com/css?family=Rationale');
.selected button {
background-color: rgba(158,158,158,0.2);
background-color: rgba(158,158,158,0.2);
}
md-card-content.terminal-container {
@@ -49,3 +49,13 @@ md-input-container .md-errors-spacer {
.stats {
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);
}

View File

@@ -12,6 +12,7 @@
<form id="welcomeFormBypass" method="POST" action="/">
<button id="start" type="submit">Start Session</button>
<input id="stack" type="hidden" name="stack" value=""/>
<input id="stack_name" type="hidden" name="stack_name" value=""/>
</form>
</div>
</body>
@@ -37,5 +38,9 @@
if (stack) {
document.getElementById('stack').value = stack;
}
var stackName = getParameterByName('stack_name');
if (stack) {
document.getElementById('stack_name').value = stackName;
}
</script>
</html>

View File

@@ -15,6 +15,7 @@
<div id="recaptcha" class="g-recaptcha" data-callback="iAmHuman" data-sitekey="{{.}}"></div>
<input type="hidden" name="session-duration" value="4h"/>
<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>
</form>
<img src="/assets/large_h.png" />
@@ -38,6 +39,10 @@
if (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) {
document.getElementById('create').style = "";
document.getElementById('recaptcha').style = "display:none;";