<commit_before>package web

import (
    "fmt"
    "net/http"
    "net/url"
    "strings"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/contrib/renders/multitemplate"
    "github.com/gin-gonic/contrib/sessions"

    "github.com/earaujoassis/space/config"
    "github.com/earaujoassis/space/models"
    "github.com/earaujoassis/space/oauth"
    "github.com/earaujoassis/space/services"
    "github.com/earaujoassis/space/feature"
    "github.com/earaujoassis/space/utils"
)

const (
    errorURI string = "%s?error=%s&state=%s"
)

var spaceCDN string

func createCustomRender() multitemplate.Render {
    render := multitemplate.New()
    render.AddFromFiles("satellite", "web/templates/default.html", "web/templates/satellite.html")
    render.AddFromFiles("error", "web/templates/default.html", "web/templates/error.html")
    return render
}

// ExposeRoutes defines and exposes HTTP routes for a given gin.RouterGroup
//      in the WEB escope
func ExposeRoutes(router *gin.Engine) {
    var cfg config.Config = config.GetGlobalConfig()
    router.LoadHTMLGlob("web/templates/*.html")
    router.HTMLRender = createCustomRender()
    if config.IsEnvironment("production") && cfg.CdnAddr != "" {
        spaceCDN = cfg.CdnAddr
    } else {
        spaceCDN = "/public"
        router.Static("/public", "web/public")
    }
    store := sessions.NewCookieStore([]byte(cfg.SessionSecret))
    store.Options(sessions.Options{
        Secure: config.IsEnvironment("production"),
        HttpOnly: true,
    })
    router.Use(sessions.Sessions("jupiter.session", store))
    views := router.Group("/")
    {
        views.GET("/", jupiterHandler)
        views.GET("/profile", jupiterHandler)

        views.GET("/signup", func(c *gin.Context) {
            c.HTML(http.StatusOK, "satellite", utils.H{
                "AssetsEndpoint": spaceCDN,
                "Title": " - Sign up",
                "Satellite": "io",
                "UserCreateEnabled": feature.IsActive("user.create"),
                "Data": utils.H{
                    "feature.gates": utils.H{
                        "user.create": feature.IsActive("user.create"),
                    },
                },
            })
        })

        views.GET("/signin", func(c *gin.Context) {
            c.HTML(http.StatusOK, "satellite", utils.H{
                "AssetsEndpoint": spaceCDN,
                "Title": " - Sign in",
                "Satellite": "ganymede",
                "UserCreateEnabled": feature.IsActive("user.create"),
            })
        })

        views.GET("/signout", func(c *gin.Context) {
            session := sessions.Default(c)

            userPublicID := session.Get("userPublicID")
            if userPublicID != nil {
                session.Delete("userPublicID")
                session.Save()
            }

            c.Redirect(http.StatusFound, "/signin")
        })

        views.GET("/session", func(c *gin.Context) {
            session := sessions.Default(c)

            userPublicID := session.Get("userPublicID")
            if userPublicID != nil {
                c.Redirect(http.StatusFound, "/")
                return
            }

            var nextPath = "/"
            var scope = c.Query("scope")
            var grantType = c.Query("grant_type")
            var code = c.Query("code")
            var clientID = c.Query("client_id")
            var _nextPath = c.Query("_")
            //var state string = c.Query("state")

            if scope == "" || grantType == "" || code == "" || clientID == "" {
                // Original response:
                // c.String(http.StatusMethodNotAllowed, "Missing required parameters")
                c.Redirect(http.StatusFound, "/signin")
                return
            }
            if _nextPath != "" {
                if _nextPath, err := url.QueryUnescape(_nextPath); err == nil {
                    nextPath = _nextPath
                }
            }

            client := services.FindOrCreateClient("Jupiter")
            if client.Key == clientID && grantType == oauth.AuthorizationCode && scope == models.PublicScope {
                grantToken := services.FindSessionByToken(code, models.GrantToken)
                if grantToken.ID != 0 {
                    session.Set("userPublicID", grantToken.User.PublicID)
                    session.Save()
                    services.InvalidateSession(grantToken)
                    c.Redirect(http.StatusFound, nextPath)
                    return
                }
            }

            c.Redirect(http.StatusFound, "/signin")
        })

        views.GET("/authorize", authorizeHandler)
        views.POST("/authorize", authorizeHandler)

        views.GET("/error", func(c *gin.Context) {
            errorReason := c.Query("response_type")

            c.HTML(http.StatusOK, "error", utils.H{
                "AssetsEndpoint": spaceCDN,
                "errorReason": errorReason,
            })
        })

        views.POST("/token", func(c *gin.Context) {
            var grantType = c.PostForm("grant_type")

            authorizationBasic := strings.Replace(c.Request.Header.Get("Authorization"), "Basic ", "", 1)
            client := oauth.ClientAuthentication(authorizationBasic)
            if client.ID == 0 {
                c.Header("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", c.Request.RequestURI))
                c.JSON(http.StatusUnauthorized, utils.H{
                    "error": oauth.AccessDenied,
                })
                return
            }

            switch grantType {
            // Authorization Code Grant
            case oauth.AuthorizationCode:
                result, err := oauth.AccessTokenRequest(utils.H{
                    "grant_type": grantType,
                    "code": c.PostForm("code"),
                    "redirect_uri": c.PostForm("redirect_uri"),
                    "client": client,
                })
                if err != nil {
                    c.JSON(http.StatusMethodNotAllowed, utils.H{
                        "error": result["error"],
                    })
                    return
                }
                c.JSON(http.StatusOK, utils.H{
                    "user_id": result["user_id"],
                    "access_token": result["access_token"],
                    "token_type": result["token_type"],
                    "expires_in": result["expires_in"],
                    "refresh_token": result["refresh_token"],
                    "scope": result["scope"],
                })
                return
            // Refreshing an Access Token
            case oauth.RefreshToken:
                result, err := oauth.RefreshTokenRequest(utils.H{
                    "grant_type": grantType,
                    "refresh_token": c.PostForm("refresh_token"),
                    "scope": c.PostForm("scope"),
                    "client": client,
                })
                if err != nil {
                    c.JSON(http.StatusMethodNotAllowed, utils.H{
                        "error": result["error"],
                    })
                    return
                }
                c.JSON(http.StatusOK, utils.H{
                    "user_id": result["user_id"],
                    "access_token": result["access_token"],
                    "token_type": result["token_type"],
                    "expires_in": result["expires_in"],
                    "refresh_token": result["refresh_token"],
                    "scope": result["scope"],
                })
                return
            // Resource Owner Password Credentials Grant
            // Client Credentials Grant
            case oauth.Password, oauth.ClientCredentials:
                c.JSON(http.StatusMethodNotAllowed, utils.H{
                    "error": oauth.UnsupportedGrantType,
                })
                return
            default:
                c.JSON(http.StatusBadRequest, utils.H{
                    "error": oauth.InvalidRequest,
                })
                return
            }
        })
    }
}

func jupiterHandler(c *gin.Context) {
    session := sessions.Default(c)
    userPublicID := session.Get("userPublicID")
    if userPublicID == nil {
        c.Redirect(http.StatusFound, "/signin")
        return
    }
    client := services.FindOrCreateClient("Jupiter")
    user := services.FindUserByPublicID(userPublicID.(string))
    actionToken := services.CreateAction(user, client,
        c.Request.RemoteAddr,
        c.Request.UserAgent(),
        models.ReadWriteScope)
    c.HTML(http.StatusOK, "satellite", utils.H{
        "AssetsEndpoint": spaceCDN,
        "Title": " - Mission control",
        "Satellite": "europa",
        "Internal": true,
        "Data": utils.H {
            "action_token": actionToken.Token,
            "user_id": user.UUID,
        },
    })
}

func authorizeHandler(c *gin.Context) {
    var location string
    var responseType string
    var clientID string
    var redirectURI string
    var scope string
    var state string

    session := sessions.Default(c)
    userPublicID := session.Get("userPublicID")
    nextPath := url.QueryEscape(fmt.Sprintf("%s?%s", c.Request.URL.Path, c.Request.URL.RawQuery))
    if userPublicID == nil {
        location = fmt.Sprintf("/signin?_=%s", nextPath)
        c.Redirect(http.StatusFound, location)
        return
    }
    user := services.FindUserByPublicID(userPublicID.(string))
    if user.ID == 0 {
        session.Delete("userPublicID")
        session.Save()
        location = fmt.Sprintf("/signin?_=%s", nextPath)
        c.Redirect(http.StatusFound, location)
        return
    }

    responseType = c.Query("response_type")
    clientID = c.Query("client_id")
    redirectURI = c.Query("redirect_uri")
    scope = c.Query("scope")
    state = c.Query("state")

    if redirectURI == "" {
        redirectURI = "/error"
    }

    client := services.FindClientByKey(clientID)
    if client.ID == 0 {
        redirectURI = "/error"
        location = fmt.Sprintf("%s?error=%s&state=%s",
            redirectURI, oauth.UnauthorizedClient, state)
        c.Redirect(http.StatusFound, location)
        return
    }

    if scope != models.PublicScope && scope != models.ReadScope && scope != models.ReadWriteScope {
        scope = "public"
    }

    switch responseType {
    // Authorization Code Grant
    case oauth.Code:
        activeSessions := services.ActiveSessionsForClient(client.ID, user.ID)
        if c.Request.Method == "GET" && activeSessions == 0 {
            c.HTML(http.StatusOK, "satellite", utils.H{
                "AssetsEndpoint": spaceCDN,
                "Title": " - Authorize",
                "Satellite": "callisto",
                "Internal": true,
                "Data": utils.H{
                    "first_name": user.FirstName,
                    "last_name": user.LastName,
                    "client_name": client.Name,
                    "client_uri": client.CanonicalURI,
                    "requested_scope": scope,
                },
            })
            return
        } else if c.Request.Method == "POST" || (activeSessions > 0 && c.Request.Method == "GET") {
            if c.PostForm("access_denied") == "true" {
                location = fmt.Sprintf(errorURI, redirectURI, oauth.AccessDenied, state)
                c.Redirect(http.StatusFound, location)
                return
            }
            result, err := oauth.AuthorizationCodeGrant(utils.H{
                "response_type": responseType,
                "client": client,
                "user": user,
                "ip": c.Request.RemoteAddr,
                "userAgent": c.Request.UserAgent(),
                "redirect_uri": redirectURI,
                "scope": scope,
                "state": state,
            })
            if err != nil {
                location = fmt.Sprintf(errorURI, redirectURI, result["error"], result["state"])
                c.Redirect(http.StatusFound, location)
            } else {
                location = fmt.Sprintf("%s?code=%s&scope=%s&state=%s",
                    redirectURI, result["code"], result["scope"], result["state"])
                c.Redirect(http.StatusFound, location)
            }
        } else {
            c.String(http.StatusNotFound, "404 Not Found")
        }
    // Implicit Grant
    case oauth.Token:
        location = fmt.Sprintf(errorURI,
            redirectURI, oauth.UnsupportedResponseType, state)
        c.Redirect(http.StatusFound, location)
        return
    default:
        location = fmt.Sprintf(errorURI,
            redirectURI, oauth.InvalidRequest, state)
        c.Redirect(http.StatusFound, location)
        return
    }
}
<commit_msg>feature: serve 'public' folder in production if desired<commit_after>package web

import (
    "fmt"
    "net/http"
    "net/url"
    "strings"

    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/contrib/renders/multitemplate"
    "github.com/gin-gonic/contrib/sessions"

    "github.com/earaujoassis/space/config"
    "github.com/earaujoassis/space/models"
    "github.com/earaujoassis/space/oauth"
    "github.com/earaujoassis/space/services"
    "github.com/earaujoassis/space/feature"
    "github.com/earaujoassis/space/utils"
)

const (
    errorURI string = "%s?error=%s&state=%s"
)

var spaceCDN string

func createCustomRender() multitemplate.Render {
    render := multitemplate.New()
    render.AddFromFiles("satellite", "web/templates/default.html", "web/templates/satellite.html")
    render.AddFromFiles("error", "web/templates/default.html", "web/templates/error.html")
    return render
}

// ExposeRoutes defines and exposes HTTP routes for a given gin.RouterGroup
//      in the WEB escope
func ExposeRoutes(router *gin.Engine) {
    var cfg config.Config = config.GetGlobalConfig()
    router.LoadHTMLGlob("web/templates/*.html")
    router.HTMLRender = createCustomRender()
    if cfg.CdnAddr != "/public" {
        spaceCDN = cfg.CdnAddr
    } else {
        spaceCDN = "/public"
        router.Static("/public", "web/public")
    }
    store := sessions.NewCookieStore([]byte(cfg.SessionSecret))
    store.Options(sessions.Options{
        Secure: config.IsEnvironment("production"),
        HttpOnly: true,
    })
    router.Use(sessions.Sessions("jupiter.session", store))
    views := router.Group("/")
    {
        views.GET("/", jupiterHandler)
        views.GET("/profile", jupiterHandler)

        views.GET("/signup", func(c *gin.Context) {
            c.HTML(http.StatusOK, "satellite", utils.H{
                "AssetsEndpoint": spaceCDN,
                "Title": " - Sign up",
                "Satellite": "io",
                "UserCreateEnabled": feature.IsActive("user.create"),
                "Data": utils.H{
                    "feature.gates": utils.H{
                        "user.create": feature.IsActive("user.create"),
                    },
                },
            })
        })

        views.GET("/signin", func(c *gin.Context) {
            c.HTML(http.StatusOK, "satellite", utils.H{
                "AssetsEndpoint": spaceCDN,
                "Title": " - Sign in",
                "Satellite": "ganymede",
                "UserCreateEnabled": feature.IsActive("user.create"),
            })
        })

        views.GET("/signout", func(c *gin.Context) {
            session := sessions.Default(c)

            userPublicID := session.Get("userPublicID")
            if userPublicID != nil {
                session.Delete("userPublicID")
                session.Save()
            }

            c.Redirect(http.StatusFound, "/signin")
        })

        views.GET("/session", func(c *gin.Context) {
            session := sessions.Default(c)

            userPublicID := session.Get("userPublicID")
            if userPublicID != nil {
                c.Redirect(http.StatusFound, "/")
                return
            }

            var nextPath = "/"
            var scope = c.Query("scope")
            var grantType = c.Query("grant_type")
            var code = c.Query("code")
            var clientID = c.Query("client_id")
            var _nextPath = c.Query("_")
            //var state string = c.Query("state")

            if scope == "" || grantType == "" || code == "" || clientID == "" {
                // Original response:
                // c.String(http.StatusMethodNotAllowed, "Missing required parameters")
                c.Redirect(http.StatusFound, "/signin")
                return
            }
            if _nextPath != "" {
                if _nextPath, err := url.QueryUnescape(_nextPath); err == nil {
                    nextPath = _nextPath
                }
            }

            client := services.FindOrCreateClient("Jupiter")
            if client.Key == clientID && grantType == oauth.AuthorizationCode && scope == models.PublicScope {
                grantToken := services.FindSessionByToken(code, models.GrantToken)
                if grantToken.ID != 0 {
                    session.Set("userPublicID", grantToken.User.PublicID)
                    session.Save()
                    services.InvalidateSession(grantToken)
                    c.Redirect(http.StatusFound, nextPath)
                    return
                }
            }

            c.Redirect(http.StatusFound, "/signin")
        })

        views.GET("/authorize", authorizeHandler)
        views.POST("/authorize", authorizeHandler)

        views.GET("/error", func(c *gin.Context) {
            errorReason := c.Query("response_type")

            c.HTML(http.StatusOK, "error", utils.H{
                "AssetsEndpoint": spaceCDN,
                "errorReason": errorReason,
            })
        })

        views.POST("/token", func(c *gin.Context) {
            var grantType = c.PostForm("grant_type")

            authorizationBasic := strings.Replace(c.Request.Header.Get("Authorization"), "Basic ", "", 1)
            client := oauth.ClientAuthentication(authorizationBasic)
            if client.ID == 0 {
                c.Header("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s\"", c.Request.RequestURI))
                c.JSON(http.StatusUnauthorized, utils.H{
                    "error": oauth.AccessDenied,
                })
                return
            }

            switch grantType {
            // Authorization Code Grant
            case oauth.AuthorizationCode:
                result, err := oauth.AccessTokenRequest(utils.H{
                    "grant_type": grantType,
                    "code": c.PostForm("code"),
                    "redirect_uri": c.PostForm("redirect_uri"),
                    "client": client,
                })
                if err != nil {
                    c.JSON(http.StatusMethodNotAllowed, utils.H{
                        "error": result["error"],
                    })
                    return
                }
                c.JSON(http.StatusOK, utils.H{
                    "user_id": result["user_id"],
                    "access_token": result["access_token"],
                    "token_type": result["token_type"],
                    "expires_in": result["expires_in"],
                    "refresh_token": result["refresh_token"],
                    "scope": result["scope"],
                })
                return
            // Refreshing an Access Token
            case oauth.RefreshToken:
                result, err := oauth.RefreshTokenRequest(utils.H{
                    "grant_type": grantType,
                    "refresh_token": c.PostForm("refresh_token"),
                    "scope": c.PostForm("scope"),
                    "client": client,
                })
                if err != nil {
                    c.JSON(http.StatusMethodNotAllowed, utils.H{
                        "error": result["error"],
                    })
                    return
                }
                c.JSON(http.StatusOK, utils.H{
                    "user_id": result["user_id"],
                    "access_token": result["access_token"],
                    "token_type": result["token_type"],
                    "expires_in": result["expires_in"],
                    "refresh_token": result["refresh_token"],
                    "scope": result["scope"],
                })
                return
            // Resource Owner Password Credentials Grant
            // Client Credentials Grant
            case oauth.Password, oauth.ClientCredentials:
                c.JSON(http.StatusMethodNotAllowed, utils.H{
                    "error": oauth.UnsupportedGrantType,
                })
                return
            default:
                c.JSON(http.StatusBadRequest, utils.H{
                    "error": oauth.InvalidRequest,
                })
                return
            }
        })
    }
}

func jupiterHandler(c *gin.Context) {
    session := sessions.Default(c)
    userPublicID := session.Get("userPublicID")
    if userPublicID == nil {
        c.Redirect(http.StatusFound, "/signin")
        return
    }
    client := services.FindOrCreateClient("Jupiter")
    user := services.FindUserByPublicID(userPublicID.(string))
    actionToken := services.CreateAction(user, client,
        c.Request.RemoteAddr,
        c.Request.UserAgent(),
        models.ReadWriteScope)
    c.HTML(http.StatusOK, "satellite", utils.H{
        "AssetsEndpoint": spaceCDN,
        "Title": " - Mission control",
        "Satellite": "europa",
        "Internal": true,
        "Data": utils.H {
            "action_token": actionToken.Token,
            "user_id": user.UUID,
        },
    })
}

func authorizeHandler(c *gin.Context) {
    var location string
    var responseType string
    var clientID string
    var redirectURI string
    var scope string
    var state string

    session := sessions.Default(c)
    userPublicID := session.Get("userPublicID")
    nextPath := url.QueryEscape(fmt.Sprintf("%s?%s", c.Request.URL.Path, c.Request.URL.RawQuery))
    if userPublicID == nil {
        location = fmt.Sprintf("/signin?_=%s", nextPath)
        c.Redirect(http.StatusFound, location)
        return
    }
    user := services.FindUserByPublicID(userPublicID.(string))
    if user.ID == 0 {
        session.Delete("userPublicID")
        session.Save()
        location = fmt.Sprintf("/signin?_=%s", nextPath)
        c.Redirect(http.StatusFound, location)
        return
    }

    responseType = c.Query("response_type")
    clientID = c.Query("client_id")
    redirectURI = c.Query("redirect_uri")
    scope = c.Query("scope")
    state = c.Query("state")

    if redirectURI == "" {
        redirectURI = "/error"
    }

    client := services.FindClientByKey(clientID)
    if client.ID == 0 {
        redirectURI = "/error"
        location = fmt.Sprintf("%s?error=%s&state=%s",
            redirectURI, oauth.UnauthorizedClient, state)
        c.Redirect(http.StatusFound, location)
        return
    }

    if scope != models.PublicScope && scope != models.ReadScope && scope != models.ReadWriteScope {
        scope = "public"
    }

    switch responseType {
    // Authorization Code Grant
    case oauth.Code:
        activeSessions := services.ActiveSessionsForClient(client.ID, user.ID)
        if c.Request.Method == "GET" && activeSessions == 0 {
            c.HTML(http.StatusOK, "satellite", utils.H{
                "AssetsEndpoint": spaceCDN,
                "Title": " - Authorize",
                "Satellite": "callisto",
                "Internal": true,
                "Data": utils.H{
                    "first_name": user.FirstName,
                    "last_name": user.LastName,
                    "client_name": client.Name,
                    "client_uri": client.CanonicalURI,
                    "requested_scope": scope,
                },
            })
            return
        } else if c.Request.Method == "POST" || (activeSessions > 0 && c.Request.Method == "GET") {
            if c.PostForm("access_denied") == "true" {
                location = fmt.Sprintf(errorURI, redirectURI, oauth.AccessDenied, state)
                c.Redirect(http.StatusFound, location)
                return
            }
            result, err := oauth.AuthorizationCodeGrant(utils.H{
                "response_type": responseType,
                "client": client,
                "user": user,
                "ip": c.Request.RemoteAddr,
                "userAgent": c.Request.UserAgent(),
                "redirect_uri": redirectURI,
                "scope": scope,
                "state": state,
            })
            if err != nil {
                location = fmt.Sprintf(errorURI, redirectURI, result["error"], result["state"])
                c.Redirect(http.StatusFound, location)
            } else {
                location = fmt.Sprintf("%s?code=%s&scope=%s&state=%s",
                    redirectURI, result["code"], result["scope"], result["state"])
                c.Redirect(http.StatusFound, location)
            }
        } else {
            c.String(http.StatusNotFound, "404 Not Found")
        }
    // Implicit Grant
    case oauth.Token:
        location = fmt.Sprintf(errorURI,
            redirectURI, oauth.UnsupportedResponseType, state)
        c.Redirect(http.StatusFound, location)
        return
    default:
        location = fmt.Sprintf(errorURI,
            redirectURI, oauth.InvalidRequest, state)
        c.Redirect(http.StatusFound, location)
        return
    }
}
<|endoftext|>


<commit_before>/*
 Copyright 2016 Padduck, LLC

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 	http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package environments

import (
	"errors"

	ppError "github.com/pufferpanel/pufferd/errors"
	"github.com/docker/docker/client"
	"context"
	"github.com/pufferpanel/apufferi/logging"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/docker/api/types/network"
	"io"
	"io/ioutil"
	"time"
	"runtime"
	"os"
	"fmt"
)

type docker struct {
	*BaseEnvironment
	ContainerId string `json:"-"`
	ImageName   string `json:"image"`
	connection  types.HijackedResponse
	cli			*client.Client
	downloadingImage bool
}

func createDocker(containerId, imageName string) *docker {
	if imageName == "" {
		imageName = "pufferpanel/generic"
	}
	d := &docker{BaseEnvironment: &BaseEnvironment{Type: "docker"}, ContainerId: containerId, ImageName: imageName}
	d.BaseEnvironment.executeAsync = d.dockerExecuteAsync
	d.BaseEnvironment.waitForMainProcess = d.WaitForMainProcess
	return d
}

func (d *docker) dockerExecuteAsync(cmd string, args []string, callback func(graceful bool)) (error) {
	running, err := d.IsRunning()
	if err != nil {
		return err

	}
	if running {
		return errors.New("container is already running")
	}

	if d.downloadingImage {
		return errors.New("container image is downloading, cannot execute")
	}

	client, err := d.getClient()
	ctx := context.Background()

	exists, err := d.doesContainerExist(client, ctx)

	if err != nil {
		return err
	}

	//container does not exist
	if !exists {
		err = d.createContainer(client, ctx, cmd, args, d.RootDirectory)
		if err != nil {
			return err
		}
	}

	config := types.ContainerAttachOptions{
		Stdin:  true,
		Stdout: true,
		Stderr: true,
		Stream: true,
	}

	d.connection, err = client.ContainerAttach(ctx, d.ContainerId, config)
	if err != nil {
		return err
	}

	d.wait.Add(1)

	go func() {
		defer d.connection.Close()
		wrapper := d.createWrapper()
		io.Copy(wrapper, d.connection.Reader)
		c, _ := d.getClient()
		c.ContainerStop(context.Background(), d.ContainerId, nil)
		time.Sleep(1 * time.Second)
		d.wait.Done()
	}()

	startOpts := types.ContainerStartOptions{
	}

	err = client.ContainerStart(ctx, d.ContainerId, startOpts)
	if err != nil {
		return err
	}
	return err
}

func (d *docker) ExecuteInMainProcess(cmd string) (err error) {
	running, err := d.IsRunning()
	if err != nil {
		return
	}
	if !running {
		err = errors.New("main process has not been started")
		return
	}

	d.connection.Conn.Write([]byte(cmd + "\n"))
	return
}

func (d *docker) Kill() (err error) {
	running, err := d.IsRunning()
	if err != nil {
		return err
	}

	if !running {
		return
	}

	client, err := d.getClient()
	ctx := context.Background()
	err = client.ContainerKill(ctx, d.ContainerId, "SIGKILL")
	return
}

func (d *docker) Create() error {
	err := os.Mkdir(d.RootDirectory, 0755)
	if err != nil {
		return err
	}

	go func() {
		cli, err := d.getClient()
		if err != nil {
			return
		}
		err = d.pullImage(cli, context.Background(), false)
	}()

	return err
}

func (d *docker) IsRunning() (bool, error) {
	client, err := d.getClient()
	if err != nil {
		logging.Error("Error checking run status", err)
		return false, err
	}

	ctx := context.Background()

	exists, err := d.doesContainerExist(client, ctx)
	if !exists {
		return false, err
	}

	stats, err := client.ContainerInspect(ctx, d.ContainerId)
	if err != nil {
		logging.Error("Error checking run status", err)
		return false, err
	}
	return stats.State.Running, nil
}

func (d *docker) GetStats() (map[string]interface{}, error) {
	running, err := d.IsRunning()
	if err != nil {
		return nil, err
	}

	if !running {
		return nil, ppError.NewServerOffline()
	}

	resultMap := make(map[string]interface{})
	resultMap["memory"] = 0
	resultMap["cpu"] = 0
	return resultMap, nil
}

func (e *docker) WaitForMainProcess() error {
	return e.WaitForMainProcessFor(0)
}

func (e *docker) WaitForMainProcessFor(timeout int) (err error) {
	running, err := e.IsRunning()
	if err != nil {
		return
	}
	if running {
		if timeout > 0 {
			var timer = time.AfterFunc(time.Duration(timeout)*time.Millisecond, func() {
				err = e.Kill()
			})
			e.wait.Wait()
			timer.Stop()
		} else {
			e.wait.Wait()
		}
	}
	return
}

func (d *docker) getClient() (*client.Client, error) {
	var err error = nil
	if d.cli == nil {
		d.cli, err = client.NewEnvClient()
	}
	return d.cli, err
}

func (d *docker) doesContainerExist(client *client.Client, ctx context.Context) (bool, error) {
	opts := types.ContainerListOptions{
		Filters: filters.NewArgs(),
	}

	opts.All = true
	opts.Filters.Add("name", d.ContainerId)

	existingContainers, err := client.ContainerList(ctx, opts)

	logging.Debugf("Does container (%s) exist?: %t", d.ContainerId, len(existingContainers) > 0)

	if len(existingContainers) == 0 {
		return false, err
	} else {
		return true, err
	}
}

func (d *docker) pullImage(client *client.Client, ctx context.Context, force bool) error {
	exists := false

	opts := types.ImageListOptions{
		All:     true,
		Filters: filters.NewArgs(),
	}
	opts.Filters.Add("reference", d.ImageName)
	images, err := client.ImageList(ctx, opts)

	if err != nil {
		return err
	}

	if len(images) == 1 {
		exists = true
	}

	logging.Debugf("Does image %v exist? %v", d.ImageName, exists)

	if exists && !force {
		return nil
	}

	op := types.ImagePullOptions{
	}

	logging.Debugf("Downloading image %v", d.ImageName)
	d.DisplayToConsole("Downloading image for container, please wait\n")

	d.downloadingImage = true

	r, err := client.ImagePull(ctx, d.ImageName, op)
	defer r.Close()
	if err != nil {
		return err
	}
	_, err = io.Copy(ioutil.Discard, r)

	d.downloadingImage = false
	logging.Debugf("Downloaded image %v", d.ImageName)
	d.DisplayToConsole("Downloaded image for container\n")
	return err
}

func (d *docker) createContainer(client *client.Client, ctx context.Context, cmd string, args []string, root string) error {
	err := d.pullImage(client, ctx, false)

	if err != nil {
		return err
	}

	cmdSlice := strslice.StrSlice{}

	cmdSlice = append(cmdSlice, cmd)

	for _, v := range args {
		cmdSlice = append(cmdSlice, v)
	}

	config := &container.Config{
		AttachStderr:    true,
		AttachStdin:     true,
		AttachStdout:    true,
		Tty:             true,
		OpenStdin:       true,
		NetworkDisabled: false,
		Cmd:             cmdSlice,
		Image:           d.ImageName,
		WorkingDir:      "/server/",
	}

	if runtime.GOOS == "linux" {
		config.User = fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid())
	}

	hostConfig := &container.HostConfig{
		AutoRemove:  true,
		NetworkMode: "host",
		Resources: container.Resources{
		},
		Binds: make([]string, 0),
	}
	hostConfig.Binds = append(hostConfig.Binds, root+":"+"/server/")

	networkConfig := &network.NetworkingConfig{
	}

	_, err = client.ContainerCreate(ctx, config, hostConfig, networkConfig, d.ContainerId)
	return err
}
<commit_msg>Look for at least one image<commit_after>/*
 Copyright 2016 Padduck, LLC

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 	http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

package environments

import (
	"errors"

	ppError "github.com/pufferpanel/pufferd/errors"
	"github.com/docker/docker/client"
	"context"
	"github.com/pufferpanel/apufferi/logging"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/strslice"
	"github.com/docker/docker/api/types/network"
	"io"
	"io/ioutil"
	"time"
	"runtime"
	"os"
	"fmt"
)

type docker struct {
	*BaseEnvironment
	ContainerId string `json:"-"`
	ImageName   string `json:"image"`
	connection  types.HijackedResponse
	cli			*client.Client
	downloadingImage bool
}

func createDocker(containerId, imageName string) *docker {
	if imageName == "" {
		imageName = "pufferpanel/generic"
	}
	d := &docker{BaseEnvironment: &BaseEnvironment{Type: "docker"}, ContainerId: containerId, ImageName: imageName}
	d.BaseEnvironment.executeAsync = d.dockerExecuteAsync
	d.BaseEnvironment.waitForMainProcess = d.WaitForMainProcess
	return d
}

func (d *docker) dockerExecuteAsync(cmd string, args []string, callback func(graceful bool)) (error) {
	running, err := d.IsRunning()
	if err != nil {
		return err

	}
	if running {
		return errors.New("container is already running")
	}

	if d.downloadingImage {
		return errors.New("container image is downloading, cannot execute")
	}

	client, err := d.getClient()
	ctx := context.Background()

	exists, err := d.doesContainerExist(client, ctx)

	if err != nil {
		return err
	}

	//container does not exist
	if !exists {
		err = d.createContainer(client, ctx, cmd, args, d.RootDirectory)
		if err != nil {
			return err
		}
	}

	config := types.ContainerAttachOptions{
		Stdin:  true,
		Stdout: true,
		Stderr: true,
		Stream: true,
	}

	d.connection, err = client.ContainerAttach(ctx, d.ContainerId, config)
	if err != nil {
		return err
	}

	d.wait.Add(1)

	go func() {
		defer d.connection.Close()
		wrapper := d.createWrapper()
		io.Copy(wrapper, d.connection.Reader)
		c, _ := d.getClient()
		c.ContainerStop(context.Background(), d.ContainerId, nil)
		time.Sleep(1 * time.Second)
		d.wait.Done()
	}()

	startOpts := types.ContainerStartOptions{
	}

	err = client.ContainerStart(ctx, d.ContainerId, startOpts)
	if err != nil {
		return err
	}
	return err
}

func (d *docker) ExecuteInMainProcess(cmd string) (err error) {
	running, err := d.IsRunning()
	if err != nil {
		return
	}
	if !running {
		err = errors.New("main process has not been started")
		return
	}

	d.connection.Conn.Write([]byte(cmd + "\n"))
	return
}

func (d *docker) Kill() (err error) {
	running, err := d.IsRunning()
	if err != nil {
		return err
	}

	if !running {
		return
	}

	client, err := d.getClient()
	ctx := context.Background()
	err = client.ContainerKill(ctx, d.ContainerId, "SIGKILL")
	return
}

func (d *docker) Create() error {
	err := os.Mkdir(d.RootDirectory, 0755)
	if err != nil {
		return err
	}

	go func() {
		cli, err := d.getClient()
		if err != nil {
			return
		}
		err = d.pullImage(cli, context.Background(), false)
	}()

	return err
}

func (d *docker) IsRunning() (bool, error) {
	client, err := d.getClient()
	if err != nil {
		logging.Error("Error checking run status", err)
		return false, err
	}

	ctx := context.Background()

	exists, err := d.doesContainerExist(client, ctx)
	if !exists {
		return false, err
	}

	stats, err := client.ContainerInspect(ctx, d.ContainerId)
	if err != nil {
		logging.Error("Error checking run status", err)
		return false, err
	}
	return stats.State.Running, nil
}

func (d *docker) GetStats() (map[string]interface{}, error) {
	running, err := d.IsRunning()
	if err != nil {
		return nil, err
	}

	if !running {
		return nil, ppError.NewServerOffline()
	}

	resultMap := make(map[string]interface{})
	resultMap["memory"] = 0
	resultMap["cpu"] = 0
	return resultMap, nil
}

func (e *docker) WaitForMainProcess() error {
	return e.WaitForMainProcessFor(0)
}

func (e *docker) WaitForMainProcessFor(timeout int) (err error) {
	running, err := e.IsRunning()
	if err != nil {
		return
	}
	if running {
		if timeout > 0 {
			var timer = time.AfterFunc(time.Duration(timeout)*time.Millisecond, func() {
				err = e.Kill()
			})
			e.wait.Wait()
			timer.Stop()
		} else {
			e.wait.Wait()
		}
	}
	return
}

func (d *docker) getClient() (*client.Client, error) {
	var err error = nil
	if d.cli == nil {
		d.cli, err = client.NewEnvClient()
	}
	return d.cli, err
}

func (d *docker) doesContainerExist(client *client.Client, ctx context.Context) (bool, error) {
	opts := types.ContainerListOptions{
		Filters: filters.NewArgs(),
	}

	opts.All = true
	opts.Filters.Add("name", d.ContainerId)

	existingContainers, err := client.ContainerList(ctx, opts)

	logging.Debugf("Does container (%s) exist?: %t", d.ContainerId, len(existingContainers) > 0)

	if len(existingContainers) == 0 {
		return false, err
	} else {
		return true, err
	}
}

func (d *docker) pullImage(client *client.Client, ctx context.Context, force bool) error {
	exists := false

	opts := types.ImageListOptions{
		All:     true,
		Filters: filters.NewArgs(),
	}
	opts.Filters.Add("reference", d.ImageName)
	images, err := client.ImageList(ctx, opts)

	if err != nil {
		return err
	}

	if len(images) >= 1 {
		exists = true
	}

	logging.Debugf("Does image %v exist? %v", d.ImageName, exists)

	if exists && !force {
		return nil
	}

	op := types.ImagePullOptions{
	}

	logging.Debugf("Downloading image %v", d.ImageName)
	d.DisplayToConsole("Downloading image for container, please wait\n")

	d.downloadingImage = true

	r, err := client.ImagePull(ctx, d.ImageName, op)
	defer r.Close()
	if err != nil {
		return err
	}
	_, err = io.Copy(ioutil.Discard, r)

	d.downloadingImage = false
	logging.Debugf("Downloaded image %v", d.ImageName)
	d.DisplayToConsole("Downloaded image for container\n")
	return err
}

func (d *docker) createContainer(client *client.Client, ctx context.Context, cmd string, args []string, root string) error {
	err := d.pullImage(client, ctx, false)

	if err != nil {
		return err
	}

	cmdSlice := strslice.StrSlice{}

	cmdSlice = append(cmdSlice, cmd)

	for _, v := range args {
		cmdSlice = append(cmdSlice, v)
	}

	config := &container.Config{
		AttachStderr:    true,
		AttachStdin:     true,
		AttachStdout:    true,
		Tty:             true,
		OpenStdin:       true,
		NetworkDisabled: false,
		Cmd:             cmdSlice,
		Image:           d.ImageName,
		WorkingDir:      "/server/",
	}

	if runtime.GOOS == "linux" {
		config.User = fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid())
	}

	hostConfig := &container.HostConfig{
		AutoRemove:  true,
		NetworkMode: "host",
		Resources: container.Resources{
		},
		Binds: make([]string, 0),
	}
	hostConfig.Binds = append(hostConfig.Binds, root+":"+"/server/")

	networkConfig := &network.NetworkingConfig{
	}

	_, err = client.ContainerCreate(ctx, config, hostConfig, networkConfig, d.ContainerId)
	return err
}
<|endoftext|>


<commit_before>// (c) 2013 Alexander Solovyov under terms of ISC License

package main

import (
	"encoding/json"
	"fmt"
	flags "github.com/jessevdk/go-flags"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"io/ioutil"
)

/// Globals

var Version = "0.1"

var opts struct {
	Interface string `short:"i" long:"interface" default:"" description:"ip to listen on"`
	Port      string `short:"p" long:"port" default:"8000" description:"port to listen on"`
	Log       string `short:"l" long:"log" description:"path to file for logging"`
	Config    string `short:"c" long:"config" description:"read rules from this file"`
	ShowHelp  bool   `long:"help" description:"show this help message"`
}

/// Github types

type GithubUser struct {
	Name  string
	Email string
}

type GithubRepo struct {
	Name    string
	Url     string
	Private bool
	Owner   GithubUser
}

type GithubCommit struct {
	Id        string
	Message   string
	Timestamp string
	Url       string
	Author    GithubUser
}

type GithubPayload struct {
	Ref        string
	Repository GithubRepo
	Commits    []GithubCommit
}

/// Config types

type Rule struct {
	Branch  string
	Command string
}

type Rules []Rule

type Config map[string]Rules

/// Config

func (c Config) Parse(input []string) error {
	RuleRe := regexp.MustCompile("(.+/.+):(.+)=(.+)")

	for _, line := range input {
		bits := RuleRe.FindStringSubmatch(line)
		if len(bits) != 4 {
			return fmt.Errorf("Can't parse line '%s'", line)
		}

		name := bits[1]
		if _, ok := c[name]; !ok {
			c[name] = make(Rules, 0)
		}
		c[name] = append(c[name], Rule{bits[2], bits[3]})
	}

	return nil
}

func (c Config) HandleRequest(w http.ResponseWriter, r *http.Request) {
	var data GithubPayload
	err := json.Unmarshal([]byte(r.FormValue("payload")), &data)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	name := fmt.Sprintf("%s/%s",
		data.Repository.Owner.Name, data.Repository.Name)
	rules, ok := c[name]
	if !ok {
		return
	}

	branch := strings.TrimPrefix(data.Ref, "refs/heads/")

	executed := 0
	for _, rule := range rules {
		if rule.Branch == branch {
			run(rule.Command, name, &data)
			executed += 1
		}
	}
	if executed == 0 {
		log.Printf("No handlers for %s", name)
	}
}

func run(command string, repo string, data *GithubPayload) error {
	cmd := exec.Command("sh", "-c", command)

	commit := data.Commits[0]
	cmd.Env = []string{
		env("REPO", repo),
		env("REPO_URL", data.Repository.Url),
		env("PRIVATE", fmt.Sprintf("%t", data.Repository.Private)),
		env("BRANCH", data.Ref),
		env("COMMIT", commit.Id),
		env("COMMIT_MESSAGE", commit.Message),
		env("COMMIT_TIME", commit.Timestamp),
		env("COMMIT_AUTHOR", commit.Author.Name),
		env("COMMIT_URL", commit.Url),
	}

	out, err := cmd.CombinedOutput()
	log.Printf("'%s' for %s output: %s", command, repo, out)
	return err
}

func env(key string, value string) string {
	return fmt.Sprintf("%s=%s", key, value)
}

/// Main

func main() {
	argparser := flags.NewParser(&opts, flags.PrintErrors|flags.PassDoubleDash)

	args, err := argparser.Parse()
	if err != nil {
		return
	}

	argparser.Usage = `[OPTIONS] user/repo:branch=command [more rules...]`

	if opts.ShowHelp || (len(args) == 0 && opts.Config == "") {
		argparser.WriteHelp(os.Stdout)
		return
	}

	configureLogging(opts.Log)

	cfg := make(Config)
	if len(args) > 0 {
		errhandle(cfg.Parse(args), "")
	}
	if opts.Config != "" {
		data, err := ioutil.ReadFile(opts.Config)
		errhandle(err, "")
		bits := strings.Split(strings.TrimSpace(string(data)), "\n")
		errhandle(cfg.Parse(bits), "")
	}

	http.HandleFunc("/", cfg.HandleRequest)
	http.ListenAndServe(opts.Interface+":"+opts.Port, nil)
}

func configureLogging(dst string) {
	if dst == "" || dst == "-" {
		return
	}

	file, err := os.OpenFile(opts.Log,
		os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	errhandle(err, "Error: cannot open log file!")
	log.SetOutput(file)
}

func errhandle(err error, msg string) {
	if err == nil {
		return
	}
	if msg == "" {
		msg = err.Error()
	}
	fmt.Fprintln(os.Stderr, msg)
	os.Exit(1)
}
<commit_msg>ability to dump whole config<commit_after>// (c) 2013 Alexander Solovyov under terms of ISC License

package main

import (
	"encoding/json"
	"fmt"
	flags "github.com/jessevdk/go-flags"
	"log"
	"net/http"
	"os"
	"os/exec"
	"regexp"
	"strings"
	"io/ioutil"
)

/// Globals

var Version = "0.1"

var opts struct {
	Interface string `short:"i" long:"interface" default:"" description:"ip to listen on"`
	Port      string `short:"p" long:"port" default:"8000" description:"port to listen on"`
	Log       string `short:"l" long:"log" description:"path to file for logging"`
	Config    string `short:"c" long:"config" description:"read rules from this file"`
	Dump      bool   `short:"d" long:"dump" description:"dump configuration to console"`
	ShowHelp  bool   `long:"help" description:"show this help message"`
}

/// Github types

type GithubUser struct {
	Name  string
	Email string
}

type GithubRepo struct {
	Name    string
	Url     string
	Private bool
	Owner   GithubUser
}

type GithubCommit struct {
	Id        string
	Message   string
	Timestamp string
	Url       string
	Author    GithubUser
}

type GithubPayload struct {
	Ref        string
	Repository GithubRepo
	Commits    []GithubCommit
}

/// Config types

type Rule struct {
	Branch  string
	Command string
}

type Rules []Rule

type Config map[string]Rules

/// Config

func (c Config) Parse(input []string) error {
	RuleRe := regexp.MustCompile("(.+/.+):(.+)=(.+)")

	for _, line := range input {
		bits := RuleRe.FindStringSubmatch(line)
		if len(bits) != 4 {
			return fmt.Errorf("Can't parse line '%s'", line)
		}

		name := bits[1]
		if _, ok := c[name]; !ok {
			c[name] = make(Rules, 0)
		}
		c[name] = append(c[name], Rule{bits[2], bits[3]})
	}

	return nil
}

func (c Config) HandleRequest(w http.ResponseWriter, r *http.Request) {
	var data GithubPayload
	err := json.Unmarshal([]byte(r.FormValue("payload")), &data)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusInternalServerError)
		return
	}

	name := fmt.Sprintf("%s/%s",
		data.Repository.Owner.Name, data.Repository.Name)
	rules, ok := c[name]
	if !ok {
		return
	}

	branch := strings.TrimPrefix(data.Ref, "refs/heads/")

	executed := 0
	for _, rule := range rules {
		if rule.Branch == branch {
			run(rule.Command, name, &data)
			executed += 1
		}
	}
	if executed == 0 {
		log.Printf("No handlers for %s", name)
	}
}

func run(command string, repo string, data *GithubPayload) error {
	cmd := exec.Command("sh", "-c", command)

	commit := data.Commits[0]
	cmd.Env = []string{
		env("REPO", repo),
		env("REPO_URL", data.Repository.Url),
		env("PRIVATE", fmt.Sprintf("%t", data.Repository.Private)),
		env("BRANCH", data.Ref),
		env("COMMIT", commit.Id),
		env("COMMIT_MESSAGE", commit.Message),
		env("COMMIT_TIME", commit.Timestamp),
		env("COMMIT_AUTHOR", commit.Author.Name),
		env("COMMIT_URL", commit.Url),
	}

	out, err := cmd.CombinedOutput()
	log.Printf("'%s' for %s output: %s", command, repo, out)
	return err
}

func env(key string, value string) string {
	return fmt.Sprintf("%s=%s", key, value)
}

/// Main

func main() {
	argparser := flags.NewParser(&opts, flags.PrintErrors|flags.PassDoubleDash)

	args, err := argparser.Parse()
	if err != nil {
		return
	}

	argparser.Usage = `[OPTIONS] user/repo:branch=command [more rules...]`

	if opts.ShowHelp || (len(args) == 0 && opts.Config == "") {
		argparser.WriteHelp(os.Stdout)
		return
	}

	configureLogging(opts.Log)

	cfg := make(Config)
	if len(args) > 0 {
		errhandle(cfg.Parse(args), "")
	}
	if opts.Config != "" {
		data, err := ioutil.ReadFile(opts.Config)
		errhandle(err, "")
		bits := strings.Split(strings.TrimSpace(string(data)), "\n")
		errhandle(cfg.Parse(bits), "")
	}

	if opts.Dump {
		for repo, rules := range cfg {
			for _, rule := range rules {
				fmt.Printf("%s:%s='%s'\n", repo, rule.Branch, rule.Command)
			}
		}
		return
	}

	http.HandleFunc("/", cfg.HandleRequest)
	http.ListenAndServe(opts.Interface+":"+opts.Port, nil)
}

func configureLogging(dst string) {
	if dst == "" || dst == "-" {
		return
	}

	file, err := os.OpenFile(opts.Log,
		os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
	errhandle(err, "Error: cannot open log file!")
	log.SetOutput(file)
}

func errhandle(err error, msg string) {
	if err == nil {
		return
	}
	if msg == "" {
		msg = err.Error()
	}
	fmt.Fprintln(os.Stderr, msg)
	os.Exit(1)
}
<|endoftext|>


<commit_before>package api

import "net/http"

type appError struct {
	Error       error
	Message     string
	Code        int
	ContentType string
}

func internalServerError(err error) *appError {
	return &appError{
		Error:   err,
		Message: "Internal server error",
		Code:    http.StatusInternalServerError,
	}
}

func notFound(err error) *appError {
	return &appError{Error: err, Code: http.StatusNotFound}
}

func badRequest(err error) *appError {
	return &appError{Error: err, Code: http.StatusBadRequest}
}

func (e *appError) AsJSON() *appError {
	e.ContentType = APPLICATION_JSON
	return e
}

func (e *appError) WithCode(code int) *appError {
	e.Code = code
	return e
}

func (e *appError) WithMessage(message string) *appError {
	e.Message = message
	return e
}

func (e *appError) WithError(err error) *appError {
	e.Error = err
	return e
}

func (e *appError) IsJSON() bool {
	return e.ContentType == APPLICATION_JSON
}
<commit_msg>Remove unused methods<commit_after>package api

import "net/http"

type appError struct {
	Error       error
	Message     string
	Code        int
	ContentType string
}

func internalServerError(err error) *appError {
	return &appError{
		Error:   err,
		Message: "Internal server error",
		Code:    http.StatusInternalServerError,
	}
}

func notFound(err error) *appError {
	return &appError{Error: err, Code: http.StatusNotFound}
}

func badRequest(err error) *appError {
	return &appError{Error: err, Code: http.StatusBadRequest}
}

func (e *appError) AsJSON() *appError {
	e.ContentType = APPLICATION_JSON
	return e
}

func (e *appError) WithMessage(message string) *appError {
	e.Message = message
	return e
}

func (e *appError) IsJSON() bool {
	return e.ContentType == APPLICATION_JSON
}
<|endoftext|>


<commit_before>package api

import (
	"fmt"
	"net/http"

	"github.com/DMarby/picsum-photos/api/handler"
	"github.com/DMarby/picsum-photos/api/params"
	"github.com/DMarby/picsum-photos/database"
	"github.com/DMarby/picsum-photos/image"
	"github.com/gorilla/mux"
	"github.com/twmb/murmur3"
)

func (a *API) imageHandler(w http.ResponseWriter, r *http.Request) *handler.Error {
	// Get the path and query parameters
	p, err := params.GetParams(r)
	if err != nil {
		return handler.BadRequest(err.Error())
	}

	// Get the image from the database
	vars := mux.Vars(r)
	imageID := vars["id"]
	databaseImage, err := a.Database.Get(imageID)
	if err != nil {
		if err == database.ErrNotFound {
			return &handler.Error{Message: err.Error(), Code: http.StatusNotFound}
		}

		a.logError(r, "error getting image from database", err)
		return handler.InternalServerError()
	}

	// Validate the parameters
	if err := params.ValidateParams(a.MaxImageSize, databaseImage, p); err != nil {
		return handler.BadRequest(err.Error())
	}

	// Default to the image width/height if 0 is passed
	width := p.Width
	height := p.Height

	if width == 0 {
		width = databaseImage.Width
	}

	if height == 0 {
		height = databaseImage.Height
	}

	// Build the image task
	task := image.NewTask(databaseImage.ID, width, height, fmt.Sprintf("Picsum ID: %s", databaseImage.ID))
	if p.Blur {
		task.Blur(p.BlurAmount)
	}

	if p.Grayscale {
		task.Grayscale()
	}

	// Process the image
	processedImage, err := a.ImageProcessor.ProcessImage(r.Context(), task)
	if err != nil {
		a.logError(r, "error processing image", err)
		return handler.InternalServerError()
	}

	// Set the headers
	w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", buildFilename(imageID, p, width, height)))
	w.Header().Set("Content-Type", "image/jpeg")
	w.Header().Set("Cache-Control", "public, max-age=2592000") // Cache for a month
	w.Header().Set("Picsum-Source-Url", databaseImage.URL)

	// Return the image
	w.Write(processedImage)

	return nil
}

func buildFilename(imageID string, p *params.Params, width int, height int) string {
	filename := fmt.Sprintf("%s-%dx%d", imageID, width, height)

	if p.Blur {
		filename += fmt.Sprintf("-blur_%d", p.BlurAmount)
	}

	if p.Grayscale {
		filename += "-grayscale"
	}

	if p.Extension == "" {
		filename += ".jpg"
	} else {
		filename += p.Extension
	}

	return filename
}

func (a *API) imageRedirectHandler(w http.ResponseWriter, r *http.Request) *handler.Error {
	// Get the path and query parameters
	p, err := params.GetParams(r)
	if err != nil {
		return handler.BadRequest(err.Error())
	}

	// Get the image ID
	vars := mux.Vars(r)
	imageID := vars["id"]

	// Redirect
	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
	http.Redirect(w, r, fmt.Sprintf("/id/%s/%d/%d%s%s", imageID, p.Width, p.Height, p.Extension, params.BuildQuery(p.Grayscale, p.Blur, p.BlurAmount)), http.StatusFound)
	return nil
}

func (a *API) randomImageRedirectHandler(w http.ResponseWriter, r *http.Request) *handler.Error {
	// Get the path and query parameters
	p, err := params.GetParams(r)
	if err != nil {
		return handler.BadRequest(err.Error())
	}

	// Get a random image
	randomImage, err := a.Database.GetRandom()
	if err != nil {
		a.logError(r, "error getting random image from database", err)
		return handler.InternalServerError()
	}

	// Redirect
	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
	http.Redirect(w, r, fmt.Sprintf("/id/%s/%d/%d%s%s", randomImage, p.Width, p.Height, p.Extension, params.BuildQuery(p.Grayscale, p.Blur, p.BlurAmount)), http.StatusFound)
	return nil
}

func (a *API) seedImageRedirectHandler(w http.ResponseWriter, r *http.Request) *handler.Error {
	// Get the path and query parameters
	p, err := params.GetParams(r)
	if err != nil {
		return handler.BadRequest(err.Error())
	}

	// Get the image seed
	vars := mux.Vars(r)
	imageSeed := vars["seed"]

	// Hash the input using murmur3
	murmurHash := murmur3.Sum64([]byte(imageSeed))

	// Get a random image by the hash
	randomImage, err := a.Database.GetRandomWithSeed(int64(murmurHash))
	if err != nil {
		a.logError(r, "error getting random image from database", err)
		return handler.InternalServerError()
	}

	// Redirect
	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
	http.Redirect(w, r, fmt.Sprintf("/id/%s/%d/%d%s%s", randomImage, p.Width, p.Height, p.Extension, params.BuildQuery(p.Grayscale, p.Blur, p.BlurAmount)), http.StatusFound)
	return nil
}
<commit_msg>Make the source header provide the Picsum ID instead<commit_after>package api

import (
	"fmt"
	"net/http"

	"github.com/DMarby/picsum-photos/api/handler"
	"github.com/DMarby/picsum-photos/api/params"
	"github.com/DMarby/picsum-photos/database"
	"github.com/DMarby/picsum-photos/image"
	"github.com/gorilla/mux"
	"github.com/twmb/murmur3"
)

func (a *API) imageHandler(w http.ResponseWriter, r *http.Request) *handler.Error {
	// Get the path and query parameters
	p, err := params.GetParams(r)
	if err != nil {
		return handler.BadRequest(err.Error())
	}

	// Get the image from the database
	vars := mux.Vars(r)
	imageID := vars["id"]
	databaseImage, err := a.Database.Get(imageID)
	if err != nil {
		if err == database.ErrNotFound {
			return &handler.Error{Message: err.Error(), Code: http.StatusNotFound}
		}

		a.logError(r, "error getting image from database", err)
		return handler.InternalServerError()
	}

	// Validate the parameters
	if err := params.ValidateParams(a.MaxImageSize, databaseImage, p); err != nil {
		return handler.BadRequest(err.Error())
	}

	// Default to the image width/height if 0 is passed
	width := p.Width
	height := p.Height

	if width == 0 {
		width = databaseImage.Width
	}

	if height == 0 {
		height = databaseImage.Height
	}

	// Build the image task
	task := image.NewTask(databaseImage.ID, width, height, fmt.Sprintf("Picsum ID: %s", databaseImage.ID))
	if p.Blur {
		task.Blur(p.BlurAmount)
	}

	if p.Grayscale {
		task.Grayscale()
	}

	// Process the image
	processedImage, err := a.ImageProcessor.ProcessImage(r.Context(), task)
	if err != nil {
		a.logError(r, "error processing image", err)
		return handler.InternalServerError()
	}

	// Set the headers
	w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", buildFilename(imageID, p, width, height)))
	w.Header().Set("Content-Type", "image/jpeg")
	w.Header().Set("Cache-Control", "public, max-age=2592000") // Cache for a month
	w.Header().Set("Picsum-ID", databaseImage.ID)

	// Return the image
	w.Write(processedImage)

	return nil
}

func buildFilename(imageID string, p *params.Params, width int, height int) string {
	filename := fmt.Sprintf("%s-%dx%d", imageID, width, height)

	if p.Blur {
		filename += fmt.Sprintf("-blur_%d", p.BlurAmount)
	}

	if p.Grayscale {
		filename += "-grayscale"
	}

	if p.Extension == "" {
		filename += ".jpg"
	} else {
		filename += p.Extension
	}

	return filename
}

func (a *API) imageRedirectHandler(w http.ResponseWriter, r *http.Request) *handler.Error {
	// Get the path and query parameters
	p, err := params.GetParams(r)
	if err != nil {
		return handler.BadRequest(err.Error())
	}

	// Get the image ID
	vars := mux.Vars(r)
	imageID := vars["id"]

	// Redirect
	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
	http.Redirect(w, r, fmt.Sprintf("/id/%s/%d/%d%s%s", imageID, p.Width, p.Height, p.Extension, params.BuildQuery(p.Grayscale, p.Blur, p.BlurAmount)), http.StatusFound)
	return nil
}

func (a *API) randomImageRedirectHandler(w http.ResponseWriter, r *http.Request) *handler.Error {
	// Get the path and query parameters
	p, err := params.GetParams(r)
	if err != nil {
		return handler.BadRequest(err.Error())
	}

	// Get a random image
	randomImage, err := a.Database.GetRandom()
	if err != nil {
		a.logError(r, "error getting random image from database", err)
		return handler.InternalServerError()
	}

	// Redirect
	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
	http.Redirect(w, r, fmt.Sprintf("/id/%s/%d/%d%s%s", randomImage, p.Width, p.Height, p.Extension, params.BuildQuery(p.Grayscale, p.Blur, p.BlurAmount)), http.StatusFound)
	return nil
}

func (a *API) seedImageRedirectHandler(w http.ResponseWriter, r *http.Request) *handler.Error {
	// Get the path and query parameters
	p, err := params.GetParams(r)
	if err != nil {
		return handler.BadRequest(err.Error())
	}

	// Get the image seed
	vars := mux.Vars(r)
	imageSeed := vars["seed"]

	// Hash the input using murmur3
	murmurHash := murmur3.Sum64([]byte(imageSeed))

	// Get a random image by the hash
	randomImage, err := a.Database.GetRandomWithSeed(int64(murmurHash))
	if err != nil {
		a.logError(r, "error getting random image from database", err)
		return handler.InternalServerError()
	}

	// Redirect
	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
	http.Redirect(w, r, fmt.Sprintf("/id/%s/%d/%d%s%s", randomImage, p.Width, p.Height, p.Extension, params.BuildQuery(p.Grayscale, p.Blur, p.BlurAmount)), http.StatusFound)
	return nil
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package tests

import (
	"errors"
	"net"
	"net/http"
	"net/url"
	"os/exec"
	"strings"
	"testing"
	"time"

	"github.com/gorilla/websocket"

	"github.com/redhat-cip/skydive/agent"
	"github.com/redhat-cip/skydive/config"
	"github.com/redhat-cip/skydive/topology/graph"
)

// FIX(safchain) has to be removed when will be able to stop agent
var globalAgent *agent.Agent

func newClient() (*websocket.Conn, error) {
	conn, err := net.Dial("tcp", "127.0.0.1:8081")
	if err != nil {
		return nil, err
	}

	endpoint := "ws://127.0.0.1:8081/ws/graph"
	u, err := url.Parse(endpoint)
	if err != nil {
		return nil, err
	}

	wsConn, _, err := websocket.NewClient(conn, u, http.Header{"Origin": {endpoint}}, 1024, 1024)
	if err != nil {
		return nil, err
	}

	return wsConn, nil
}

func connectToAgent(timeout int, onReady func(*websocket.Conn)) (*websocket.Conn, error) {
	var ws *websocket.Conn
	var err error

	t := 0
	for {
		if t > timeout {
			return nil, errors.New("Connection timeout reached")
		}

		ws, err = newClient()
		if err == nil {
			break
		}
		time.Sleep(1 * time.Second)
		t++
	}

	ready := false
	h := func(message string) error {
		err := ws.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(time.Second))
		if err != nil {
			return err
		}
		if !ready {
			ready = true
			onReady(ws)
		}
		return nil
	}
	ws.SetPingHandler(h)

	return ws, nil
}

func processGraphMessage(g *graph.Graph, m []byte) error {
	g.Lock()
	defer g.Unlock()

	msg, err := graph.UnmarshalWSMessage(m)
	if err != nil {
		return err
	}

	switch msg.Type {
	case "NodeUpdated":
		n := msg.Obj.(*graph.Node)
		node := g.GetNode(n.ID)
		if node != nil {
			g.SetMetadatas(node, n.Metadatas())
		}
	case "NodeDeleted":
		g.DelNode(msg.Obj.(*graph.Node))
	case "NodeAdded":
		n := msg.Obj.(*graph.Node)
		if g.GetNode(n.ID) == nil {
			g.AddNode(n)
		}
	case "EdgeUpdated":
		e := msg.Obj.(*graph.Edge)
		edge := g.GetEdge(e.ID)
		if edge != nil {
			g.SetMetadatas(edge, e.Metadatas())
		}
	case "EdgeDeleted":
		g.DelEdge(msg.Obj.(*graph.Edge))
	case "EdgeAdded":
		e := msg.Obj.(*graph.Edge)
		if g.GetEdge(e.ID) == nil {
			g.AddEdge(e)
		}
	}

	return nil
}

func startAgent(t *testing.T) {
	// FIX(safchain) has to be removed see comment around the variable declaration
	if globalAgent != nil {
		return
	}

	config.InitEmptyConfig()

	section, err := config.GetConfig().NewSection("agent")
	if err != nil {
		t.Fatal(err.Error())
	}
	section.NewKey("listen", "8081")
	section.NewKey("flowtable_expire", "5")

	section, err = config.GetConfig().NewSection("cache")
	if err != nil {
		t.Fatal(err.Error())
	}
	section.NewKey("expire", "300")
	section.NewKey("cleanup", "30")

	section, err = config.GetConfig().NewSection("sflow")
	if err != nil {
		t.Fatal(err.Error())
	}
	section.NewKey("listen", "5000")

	section, err = config.GetConfig().NewSection("ovs")
	if err != nil {
		t.Fatal(err.Error())
	}
	section.NewKey("ovsdb", "6400")

	globalAgent = agent.NewAgent()
	go globalAgent.Start()
}

func startTopologyClient(t *testing.T, g *graph.Graph, onReady func(*websocket.Conn), onChange func()) error {
	// ready when got a first ping
	ws, err := connectToAgent(5, onReady)
	if err != nil {
		return err
	}

	for {
		_, m, err := ws.ReadMessage()
		if err != nil {
			break
		}

		err = processGraphMessage(g, m)
		if err != nil {
			return err
		}

		onChange()
	}

	return nil
}

func testTopology(t *testing.T, g *graph.Graph, cmds []string, onChange func(ws *websocket.Conn)) {
	var ws *websocket.Conn

	cmdIndex := 0
	or := func(w *websocket.Conn) {
		ws = w

		// ready to exec the first cmd
		err := exec.Command("sudo", strings.Split(cmds[cmdIndex], " ")...).Run()
		if err != nil {
			t.Fatal(err.Error())
		}
		cmdIndex++
	}

	oc := func() {
		onChange(ws)

		// exec the following command
		if cmdIndex < len(cmds) {
			err := exec.Command("sudo", strings.Split(cmds[cmdIndex], " ")...).Run()
			if err != nil {
				t.Fatal(err.Error())
			}
			cmdIndex++
		}
	}

	err := startTopologyClient(t, g, or, oc)
	if err != nil {
		t.Fatal(err.Error())
	}
}

func testCleanup(t *testing.T, g *graph.Graph, cmds []string) {
	// cleanup side on the test
	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) == 0 && len(g.GetEdges()) == 0 {
			testPassed = true

			ws.Close()
		}
	}

	testTopology(t, g, cmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}
}

func newGraph(t *testing.T) *graph.Graph {
	backend, err := graph.NewMemoryBackend()
	if err != nil {
		t.Fatal(err.Error())
	}

	g, err := graph.NewGraph(backend)
	if err != nil {
		t.Fatal(err.Error())
	}

	return g
}

func TestBridgeOVS(t *testing.T) {
	g := newGraph(t)

	startAgent(t)

	setupCmds := []string{
		"ovs-vsctl add-br br-test1",
	}

	tearDownCmds := []string{
		"ovs-vsctl del-br br-test1",
	}

	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) >= 3 && len(g.GetEdges()) >= 2 {
			ovsbridge := g.LookupFirstNode(graph.Metadatas{"Type": "ovsbridge", "Name": "br-test1"})
			if ovsbridge == nil {
				t.Error("ovs bridge not found")
			}
			ovsports := g.LookupChildren(ovsbridge, graph.Metadatas{"Type": "ovsport"})
			if len(ovsports) != 1 {
				t.Error("ovs port not found or not unique")
			}
			devices := g.LookupChildren(ovsports[0], graph.Metadatas{"Type": "internal", "Driver": "openvswitch"})
			if len(devices) != 1 {
				t.Error("device not found or not unique")
			}

			if ovsbridge.Metadatas()["Host"] == "" || ovsports[0].Metadatas()["Host"] == "" || devices[0].Metadatas()["Host"] == "" {
				t.Error("host binding not found")
			}

			testPassed = true

			ws.Close()
		}
	}

	testTopology(t, g, setupCmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}

	testCleanup(t, g, tearDownCmds)
}

func TestPatchOVS(t *testing.T) {
	g := newGraph(t)

	startAgent(t)

	setupCmds := []string{
		"ovs-vsctl add-br br-test1",
		"ovs-vsctl add-br br-test2",
		"ovs-vsctl add-port br-test1 patch-br-test2 -- set interface patch-br-test2 type=patch",
		"ovs-vsctl add-port br-test2 patch-br-test1 -- set interface patch-br-test1 type=patch",
		"ovs-vsctl set interface patch-br-test2 option:peer=patch-br-test1",
		"ovs-vsctl set interface patch-br-test1 option:peer=patch-br-test2",
	}

	tearDownCmds := []string{
		"ovs-vsctl del-br br-test1",
		"ovs-vsctl del-br br-test2",
	}

	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) >= 10 && len(g.GetEdges()) >= 9 {
			patch1 := g.LookupFirstNode(graph.Metadatas{"Type": "patch", "Name": "patch-br-test1", "Driver": "openvswitch"})
			if patch1 == nil {
				t.Error("patch not found")
			}

			patch2 := g.LookupFirstNode(graph.Metadatas{"Type": "patch", "Name": "patch-br-test2", "Driver": "openvswitch"})
			if patch2 == nil {
				t.Error("patch not found")
			}

			if !g.AreLinked(patch1, patch2) {
				t.Error("patch interfaces not linked")
			}

			testPassed = true

			ws.Close()
		}
	}

	testTopology(t, g, setupCmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}

	testCleanup(t, g, tearDownCmds)
}

func TestInterfaceOVS(t *testing.T) {
	g := newGraph(t)

	startAgent(t)

	setupCmds := []string{
		"ovs-vsctl add-br br-test1",
		"ovs-vsctl add-port br-test1 intf1 -- set interface intf1 type=internal",
	}

	tearDownCmds := []string{
		"ovs-vsctl del-br br-test1",
	}

	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) >= 5 && len(g.GetEdges()) >= 4 {
			intf := g.LookupFirstNode(graph.Metadatas{"Type": "internal", "Name": "intf1", "Driver": "openvswitch"})
			if intf != nil {
				if _, ok := intf.Metadatas()["UUID"]; ok {
					// check we don't have another interface potentially added by netlink
					// should only have ovsport and interface
					others := g.LookupNodes(graph.Metadatas{"Name": "intf1"})
					if len(others) > 2 {
						t.Error("found more interface than expected")
					}

					if _, ok := intf.Metadatas()["MAC"]; !ok {
						t.Error("mac not found")
					}

					testPassed = true

					ws.Close()
				}
			}
		}
	}

	testTopology(t, g, setupCmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}

	testCleanup(t, g, tearDownCmds)
}

func TestBondOVS(t *testing.T) {
	g := newGraph(t)

	startAgent(t)

	setupCmds := []string{
		"ovs-vsctl add-br br-test1",
		"ip tuntap add mode tap dev intf1",
		"ip tuntap add mode tap dev intf2",
		"ovs-vsctl add-bond br-test1 bond0 intf1 intf2",
	}

	tearDownCmds := []string{
		"ovs-vsctl del-br br-test1",
		"ip link del intf1",
		"ip link del intf2",
	}

	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) >= 6 && len(g.GetEdges()) >= 5 {
			bond := g.LookupFirstNode(graph.Metadatas{"Type": "ovsport", "Name": "bond0"})
			if bond != nil {
				intfs := g.LookupChildren(bond, nil)
				if len(intfs) != 2 {
					t.Error("bond interfaces not found")
				}

				testPassed = true

				ws.Close()
			}

		}
	}

	testTopology(t, g, setupCmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}

	testCleanup(t, g, tearDownCmds)
}
<commit_msg>Set the ws pong/ping to 1s to reduce the tests time and add logs<commit_after>/*
 * Copyright (C) 2015 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package tests

import (
	"errors"
	"net"
	"net/http"
	"net/url"
	"os/exec"
	"strings"
	"testing"
	"time"

	"github.com/gorilla/websocket"

	"github.com/redhat-cip/skydive/agent"
	"github.com/redhat-cip/skydive/config"
	"github.com/redhat-cip/skydive/logging"
	"github.com/redhat-cip/skydive/topology/graph"
)

// FIX(safchain) has to be removed when will be able to stop agent
var globalAgent *agent.Agent

func newClient() (*websocket.Conn, error) {
	conn, err := net.Dial("tcp", "127.0.0.1:8081")
	if err != nil {
		return nil, err
	}

	endpoint := "ws://127.0.0.1:8081/ws/graph"
	u, err := url.Parse(endpoint)
	if err != nil {
		return nil, err
	}

	wsConn, _, err := websocket.NewClient(conn, u, http.Header{"Origin": {endpoint}}, 1024, 1024)
	if err != nil {
		return nil, err
	}

	return wsConn, nil
}

func connectToAgent(timeout int, onReady func(*websocket.Conn)) (*websocket.Conn, error) {
	var ws *websocket.Conn
	var err error

	t := 0
	for {
		if t > timeout {
			return nil, errors.New("Connection timeout reached")
		}

		ws, err = newClient()
		if err == nil {
			break
		}
		time.Sleep(1 * time.Second)
		t++
	}

	ready := false
	h := func(message string) error {
		err := ws.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(time.Second))
		if err != nil {
			return err
		}
		if !ready {
			ready = true
			onReady(ws)
		}
		return nil
	}
	ws.SetPingHandler(h)

	return ws, nil
}

func processGraphMessage(g *graph.Graph, m []byte) error {
	g.Lock()
	defer g.Unlock()

	msg, err := graph.UnmarshalWSMessage(m)
	if err != nil {
		return err
	}

	switch msg.Type {
	case "NodeUpdated":
		n := msg.Obj.(*graph.Node)
		node := g.GetNode(n.ID)
		if node != nil {
			g.SetMetadatas(node, n.Metadatas())
		}
	case "NodeDeleted":
		g.DelNode(msg.Obj.(*graph.Node))
	case "NodeAdded":
		n := msg.Obj.(*graph.Node)
		if g.GetNode(n.ID) == nil {
			g.AddNode(n)
		}
	case "EdgeUpdated":
		e := msg.Obj.(*graph.Edge)
		edge := g.GetEdge(e.ID)
		if edge != nil {
			g.SetMetadatas(edge, e.Metadatas())
		}
	case "EdgeDeleted":
		g.DelEdge(msg.Obj.(*graph.Edge))
	case "EdgeAdded":
		e := msg.Obj.(*graph.Edge)
		if g.GetEdge(e.ID) == nil {
			g.AddEdge(e)
		}
	}

	return nil
}

func startAgent(t *testing.T) {
	// FIX(safchain) has to be removed see comment around the variable declaration
	if globalAgent != nil {
		return
	}

	config.InitEmptyConfig()

	section, err := config.GetConfig().NewSection("default")
	if err != nil {
		t.Fatal(err.Error())
	}
	section.NewKey("ws_pong_timeout", "1")

	section, err = config.GetConfig().NewSection("agent")
	if err != nil {
		t.Fatal(err.Error())
	}
	section.NewKey("listen", "8081")
	section.NewKey("flowtable_expire", "5")

	section, err = config.GetConfig().NewSection("cache")
	if err != nil {
		t.Fatal(err.Error())
	}
	section.NewKey("expire", "300")
	section.NewKey("cleanup", "30")

	section, err = config.GetConfig().NewSection("sflow")
	if err != nil {
		t.Fatal(err.Error())
	}
	section.NewKey("listen", "5000")

	section, err = config.GetConfig().NewSection("ovs")
	if err != nil {
		t.Fatal(err.Error())
	}
	section.NewKey("ovsdb", "6400")

	globalAgent = agent.NewAgent()
	go globalAgent.Start()
}

func startTopologyClient(t *testing.T, g *graph.Graph, onReady func(*websocket.Conn), onChange func()) error {
	// ready when got a first ping
	ws, err := connectToAgent(5, onReady)
	if err != nil {
		return err
	}

	for {
		_, m, err := ws.ReadMessage()
		if err != nil {
			break
		}

		err = processGraphMessage(g, m)
		if err != nil {
			return err
		}

		logging.GetLogger().Debug("%s", string(m))
		logging.GetLogger().Debug("%s", g.String())

		onChange()
	}

	return nil
}

func testTopology(t *testing.T, g *graph.Graph, cmds []string, onChange func(ws *websocket.Conn)) {
	var ws *websocket.Conn

	cmdIndex := 0
	or := func(w *websocket.Conn) {
		ws = w

		// ready to exec the first cmd
		err := exec.Command("sudo", strings.Split(cmds[cmdIndex], " ")...).Run()
		if err != nil {
			t.Fatal(err.Error())
		}
		cmdIndex++
	}

	oc := func() {
		onChange(ws)

		// exec the following command
		if cmdIndex < len(cmds) {
			err := exec.Command("sudo", strings.Split(cmds[cmdIndex], " ")...).Run()
			if err != nil {
				t.Fatal(err.Error())
			}
			cmdIndex++
		}
	}

	err := startTopologyClient(t, g, or, oc)
	if err != nil {
		t.Fatal(err.Error())
	}
}

func testCleanup(t *testing.T, g *graph.Graph, cmds []string) {
	// cleanup side on the test
	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) == 0 && len(g.GetEdges()) == 0 {
			testPassed = true

			ws.Close()
		}
	}

	testTopology(t, g, cmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}
}

func newGraph(t *testing.T) *graph.Graph {
	backend, err := graph.NewMemoryBackend()
	if err != nil {
		t.Fatal(err.Error())
	}

	g, err := graph.NewGraph(backend)
	if err != nil {
		t.Fatal(err.Error())
	}

	return g
}

func TestBridgeOVS(t *testing.T) {
	g := newGraph(t)

	startAgent(t)

	setupCmds := []string{
		"ovs-vsctl add-br br-test1",
	}

	tearDownCmds := []string{
		"ovs-vsctl del-br br-test1",
	}

	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) >= 3 && len(g.GetEdges()) >= 2 {
			ovsbridge := g.LookupFirstNode(graph.Metadatas{"Type": "ovsbridge", "Name": "br-test1"})
			if ovsbridge == nil {
				t.Error("ovs bridge not found")
			}
			ovsports := g.LookupChildren(ovsbridge, graph.Metadatas{"Type": "ovsport"})
			if len(ovsports) != 1 {
				t.Error("ovs port not found or not unique")
			}
			devices := g.LookupChildren(ovsports[0], graph.Metadatas{"Type": "internal", "Driver": "openvswitch"})
			if len(devices) != 1 {
				t.Error("device not found or not unique")
			}

			if ovsbridge.Metadatas()["Host"] == "" || ovsports[0].Metadatas()["Host"] == "" || devices[0].Metadatas()["Host"] == "" {
				t.Error("host binding not found")
			}

			testPassed = true

			ws.Close()
		}
	}

	testTopology(t, g, setupCmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}

	testCleanup(t, g, tearDownCmds)
}

func TestPatchOVS(t *testing.T) {
	g := newGraph(t)

	startAgent(t)

	setupCmds := []string{
		"ovs-vsctl add-br br-test1",
		"ovs-vsctl add-br br-test2",
		"ovs-vsctl add-port br-test1 patch-br-test2 -- set interface patch-br-test2 type=patch",
		"ovs-vsctl add-port br-test2 patch-br-test1 -- set interface patch-br-test1 type=patch",
		"ovs-vsctl set interface patch-br-test2 option:peer=patch-br-test1",
		"ovs-vsctl set interface patch-br-test1 option:peer=patch-br-test2",
	}

	tearDownCmds := []string{
		"ovs-vsctl del-br br-test1",
		"ovs-vsctl del-br br-test2",
	}

	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) >= 10 && len(g.GetEdges()) >= 9 {
			patch1 := g.LookupFirstNode(graph.Metadatas{"Type": "patch", "Name": "patch-br-test1", "Driver": "openvswitch"})
			if patch1 == nil {
				t.Error("patch not found")
			}

			patch2 := g.LookupFirstNode(graph.Metadatas{"Type": "patch", "Name": "patch-br-test2", "Driver": "openvswitch"})
			if patch2 == nil {
				t.Error("patch not found")
			}

			if !g.AreLinked(patch1, patch2) {
				t.Error("patch interfaces not linked")
			}

			testPassed = true

			ws.Close()
		}
	}

	testTopology(t, g, setupCmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}

	testCleanup(t, g, tearDownCmds)
}

func TestInterfaceOVS(t *testing.T) {
	g := newGraph(t)

	startAgent(t)

	setupCmds := []string{
		"ovs-vsctl add-br br-test1",
		"ovs-vsctl add-port br-test1 intf1 -- set interface intf1 type=internal",
	}

	tearDownCmds := []string{
		"ovs-vsctl del-br br-test1",
	}

	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) >= 5 && len(g.GetEdges()) >= 4 {
			intf := g.LookupFirstNode(graph.Metadatas{"Type": "internal", "Name": "intf1", "Driver": "openvswitch"})
			if intf != nil {
				if _, ok := intf.Metadatas()["UUID"]; ok {
					// check we don't have another interface potentially added by netlink
					// should only have ovsport and interface
					others := g.LookupNodes(graph.Metadatas{"Name": "intf1"})
					if len(others) > 2 {
						t.Error("found more interface than expected")
					}

					if _, ok := intf.Metadatas()["MAC"]; !ok {
						t.Error("mac not found")
					}

					testPassed = true

					ws.Close()
				}
			}
		}
	}

	testTopology(t, g, setupCmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}

	testCleanup(t, g, tearDownCmds)
}

func TestBondOVS(t *testing.T) {
	g := newGraph(t)

	startAgent(t)

	setupCmds := []string{
		"ovs-vsctl add-br br-test1",
		"ip tuntap add mode tap dev intf1",
		"ip tuntap add mode tap dev intf2",
		"ovs-vsctl add-bond br-test1 bond0 intf1 intf2",
	}

	tearDownCmds := []string{
		"ovs-vsctl del-br br-test1",
		"ip link del intf1",
		"ip link del intf2",
	}

	testPassed := false
	onChange := func(ws *websocket.Conn) {
		g.Lock()
		defer g.Unlock()

		if !testPassed && len(g.GetNodes()) >= 6 && len(g.GetEdges()) >= 5 {
			bond := g.LookupFirstNode(graph.Metadatas{"Type": "ovsport", "Name": "bond0"})
			if bond != nil {
				intfs := g.LookupChildren(bond, nil)
				if len(intfs) != 2 {
					t.Error("bond interfaces not found")
				}

				testPassed = true

				ws.Close()
			}

		}
	}

	testTopology(t, g, setupCmds, onChange)
	if !testPassed {
		t.Error("test not executed")
	}

	testCleanup(t, g, tearDownCmds)
}
<|endoftext|>


<commit_before>package main

import "github.com/animenotifier/arn"

var items = []*arn.Item{
	&arn.Item{
		ID:    "pro-account-3",
		Name:  "PRO Account (1 season)",
		Price: 900,
		Description: `PRO account for 1 anime season (3 months).

1 month equals 300 gems.

Includes:

* Special highlight on the forums
* Access to the VIP channel on Discord
* PRO star on your profile
* High priority for your personal suggestions
* Early access to new features`,
		Icon:       "star",
		Rarity:     arn.ItemRaritySuperior,
		Order:      1,
		Consumable: true,
	},
	&arn.Item{
		ID:    "pro-account-6",
		Name:  "PRO Account (2 seasons)",
		Price: 1600,
		Description: `PRO account for 2 anime seasons (6 months).

11% less monthly costs compared to standard.

Includes:

* Special highlight on the forums
* Access to the VIP channel on Discord
* PRO star on your profile
* High priority for your personal suggestions
* Early access to new features`,
		Icon:       "star",
		Rarity:     arn.ItemRarityRare,
		Order:      2,
		Consumable: true,
	},
	&arn.Item{
		ID:    "pro-account-12",
		Name:  "PRO Account (4 seasons)",
		Price: 3000,
		Description: `PRO account for 4 anime seasons (12 months).

16% less monthly costs compared to standard.

Includes:

* Special highlight on the forums
* Access to the VIP channel on Discord
* PRO star on your profile
* High priority for your personal suggestions
* Early access to new features`,
		Icon:       "star",
		Rarity:     arn.ItemRarityUnique,
		Order:      3,
		Consumable: true,
	},
	&arn.Item{
		ID:    "pro-account-24",
		Name:  "PRO Account (8 seasons)",
		Price: 5900,
		Description: `PRO account for 8 anime seasons (24 months).

18% less monthly costs compared to standard.

Includes:

* Special highlight on the forums
* Access to the VIP channel on Discord
* PRO star on your profile
* High priority for your personal suggestions
* Early access to new features`,
		Icon:       "star",
		Rarity:     arn.ItemRarityLegendary,
		Order:      4,
		Consumable: true,
	},
	&arn.Item{
		ID:    "anime-support-ticket",
		Name:  "Anime Support Ticket",
		Price: 100,
		Description: `Support the makers of your favourite anime by using an anime support ticket.
Anime Notifier uses 10% of the money to handle the transaction fees while the remaining 90% go directly
to the studios involved in the creation of your favourite anime.

*This feature is work in progress.*`,
		Icon:       "ticket",
		Rarity:     arn.ItemRarityRare,
		Order:      5,
		Consumable: false,
	},
}

func main() {
	for _, item := range items {
		item.Save()
	}
}

//- ShopItem("PRO Account", "6 months", "1600", "star", strings.Replace(strings.Replace(proAccountMarkdown, "3 months", "6 months", 1), "1 anime season", "2 anime seasons", 1))
//- ShopItem("PRO Account", "1 year", "3000", "star", strings.Replace(strings.Replace(proAccountMarkdown, "3 months", "12 months", 1), "1 anime season", "4 anime seasons", 1))
//- ShopItem("PRO Account", "2 years", "5900", "star", strings.Replace(strings.Replace(proAccountMarkdown, "3 months", "24 months", 1), "1 anime season", "8 anime seasons", 1))
//- ShopItem("Anime Support Ticket", "", "100", "ticket", "Support the makers of your favourite anime by using an anime support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the studios involved in the creation of your favourite anime.")
//- ShopItem("Artwork Support Ticket", "", "100", "ticket", "Support the makers of your favourite artwork by using an artwork support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the creator.")
//- ShopItem("Soundtrack Support Ticket", "", "100", "ticket", "Support the makers of your favourite soundtrack by using a soundtrack support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the creator.")
//- ShopItem("AMV Support Ticket", "", "100", "ticket", "Support the makers of your favourite AMV by using an AMV support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the creator.")
<commit_msg>Updated shop items<commit_after>package main

import "github.com/animenotifier/arn"

var items = []*arn.Item{
	&arn.Item{
		ID:    "pro-account-3",
		Name:  "PRO Account (1 season)",
		Price: 900,
		Description: `PRO account for 1 anime season (3 months).

1 month equals 300 gems.

Includes:

* Special highlight on the forums
* Access to the VIP channel on Discord
* PRO star on your profile
* High priority for your personal suggestions
* Early access to new features`,
		Icon:       "star",
		Rarity:     arn.ItemRaritySuperior,
		Order:      1,
		Consumable: true,
	},
	&arn.Item{
		ID:    "pro-account-6",
		Name:  "PRO Account (2 seasons)",
		Price: 1600,
		Description: `PRO account for 2 anime seasons (6 months).

11% less monthly costs compared to standard.

Includes:

* Special highlight on the forums
* Access to the VIP channel on Discord
* PRO star on your profile
* High priority for your personal suggestions
* Early access to new features`,
		Icon:       "star",
		Rarity:     arn.ItemRarityRare,
		Order:      2,
		Consumable: true,
	},
	&arn.Item{
		ID:    "pro-account-12",
		Name:  "PRO Account (4 seasons)",
		Price: 3000,
		Description: `PRO account for 4 anime seasons (12 months).

16% less monthly costs compared to standard.

Includes:

* Special highlight on the forums
* Access to the VIP channel on Discord
* PRO star on your profile
* High priority for your personal suggestions
* Early access to new features`,
		Icon:       "star",
		Rarity:     arn.ItemRarityUnique,
		Order:      3,
		Consumable: true,
	},
	&arn.Item{
		ID:    "pro-account-24",
		Name:  "PRO Account (8 seasons)",
		Price: 5900,
		Description: `PRO account for 8 anime seasons (24 months).

18% less monthly costs compared to standard.

Includes:

* Special highlight on the forums
* Access to the VIP channel on Discord
* PRO star on your profile
* High priority for your personal suggestions
* Early access to new features`,
		Icon:       "star",
		Rarity:     arn.ItemRarityLegendary,
		Order:      4,
		Consumable: true,
	},
	&arn.Item{
		ID:    "anime-support-ticket",
		Name:  "Anime Support Ticket",
		Price: 100,
		Description: `Support the makers of your favourite anime by using an anime support ticket.
Anime Notifier uses 15% of the money to handle the transaction fees while the remaining 85% go directly
to the studios involved in the creation of your favourite anime.

*This feature is work in progress.*`,
		Icon:       "ticket",
		Rarity:     arn.ItemRarityRare,
		Order:      5,
		Consumable: false,
	},
}

func main() {
	for _, item := range items {
		item.Save()
	}
}

//- ShopItem("PRO Account", "6 months", "1600", "star", strings.Replace(strings.Replace(proAccountMarkdown, "3 months", "6 months", 1), "1 anime season", "2 anime seasons", 1))
//- ShopItem("PRO Account", "1 year", "3000", "star", strings.Replace(strings.Replace(proAccountMarkdown, "3 months", "12 months", 1), "1 anime season", "4 anime seasons", 1))
//- ShopItem("PRO Account", "2 years", "5900", "star", strings.Replace(strings.Replace(proAccountMarkdown, "3 months", "24 months", 1), "1 anime season", "8 anime seasons", 1))
//- ShopItem("Anime Support Ticket", "", "100", "ticket", "Support the makers of your favourite anime by using an anime support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the studios involved in the creation of your favourite anime.")
//- ShopItem("Artwork Support Ticket", "", "100", "ticket", "Support the makers of your favourite artwork by using an artwork support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the creator.")
//- ShopItem("Soundtrack Support Ticket", "", "100", "ticket", "Support the makers of your favourite soundtrack by using a soundtrack support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the creator.")
//- ShopItem("AMV Support Ticket", "", "100", "ticket", "Support the makers of your favourite AMV by using an AMV support ticket. Anime Notifier uses 8% of the money to handle the transaction fees while the remaining 92% go directly to the creator.")
<|endoftext|>


<commit_before>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package app

import (
	"github.com/globocom/tsuru/app/bind"
	"github.com/globocom/tsuru/provision"
	"launchpad.net/gocheck"
	"sort"
)

func (s *S) TestUnitGetName(c *gocheck.C) {
	u := Unit{Name: "abcdef", app: &App{Name: "2112"}}
	c.Assert(u.GetName(), gocheck.Equals, "abcdef")
}

func (s *S) TestUnitGetMachine(c *gocheck.C) {
	u := Unit{Machine: 10}
	c.Assert(u.GetMachine(), gocheck.Equals, u.Machine)
}

func (s *S) TestUnitGetStatus(c *gocheck.C) {
	var tests = []struct {
		input    string
		expected provision.Status
	}{
		{"started", provision.StatusStarted},
		{"pending", provision.StatusPending},
		{"creating", provision.StatusCreating},
		{"down", provision.StatusDown},
		{"error", provision.StatusError},
		{"installing", provision.StatusInstalling},
		{"creating", provision.StatusCreating},
	}
	for _, test := range tests {
		u := Unit{State: test.input}
		got := u.GetStatus()
		if got != test.expected {
			c.Errorf("u.GetStatus(): want %q, got %q.", test.expected, got)
		}
	}
}

func (s *S) TestUnitShouldBeABinderUnit(c *gocheck.C) {
	var _ bind.Unit = &Unit{}
}

func (s *S) TestUnitSliceLen(c *gocheck.C) {
	units := UnitSlice{Unit{}, Unit{}}
	c.Assert(units.Len(), gocheck.Equals, 2)
}

func (s *S) TestUnitSliceLess(c *gocheck.C) {
	units := UnitSlice{
		Unit{Name: "a", State: string(provision.StatusError)},
		Unit{Name: "b", State: string(provision.StatusDown)},
		Unit{Name: "c", State: string(provision.StatusPending)},
		Unit{Name: "d", State: string(provision.StatusCreating)},
		Unit{Name: "e", State: string(provision.StatusInstalling)},
		Unit{Name: "f", State: string(provision.StatusStarted)},
	}
	c.Assert(units.Less(0, 1), gocheck.Equals, true)
	c.Assert(units.Less(1, 2), gocheck.Equals, true)
	c.Assert(units.Less(2, 3), gocheck.Equals, true)
	c.Assert(units.Less(4, 5), gocheck.Equals, true)
	c.Assert(units.Less(5, 0), gocheck.Equals, false)
}

func (s *S) TestUnitSliceSwap(c *gocheck.C) {
	units := UnitSlice{
		Unit{Name: "b", State: string(provision.StatusDown)},
		Unit{Name: "c", State: string(provision.StatusPending)},
		Unit{Name: "a", State: string(provision.StatusError)},
		Unit{Name: "d", State: string(provision.StatusCreating)},
		Unit{Name: "e", State: string(provision.StatusInstalling)},
		Unit{Name: "f", State: string(provision.StatusStarted)},
	}
	units.Swap(0, 2)
	c.Assert(units.Less(0, 2), gocheck.Equals, true)
}

func (s *S) TestUnitSliceSort(c *gocheck.C) {
	units := UnitSlice{
		Unit{Name: "b", State: string(provision.StatusDown)},
		Unit{Name: "c", State: string(provision.StatusPending)},
		Unit{Name: "a", State: string(provision.StatusError)},
		Unit{Name: "d", State: string(provision.StatusCreating)},
		Unit{Name: "e", State: string(provision.StatusInstalling)},
		Unit{Name: "f", State: string(provision.StatusStarted)},
	}
	c.Assert(sort.IsSorted(units), gocheck.Equals, false)
	sort.Sort(units)
	c.Assert(sort.IsSorted(units), gocheck.Equals, true)
}

func (s *S) TestGenerateUnitQuotaItems(c *gocheck.C) {
	var tests = []struct {
		app  *App
		want []string
		n    int
	}{
		{&App{Name: "black"}, []string{"black-0"}, 1},
		{&App{Name: "black", Units: []Unit{{QuotaItem: "black-1"}, {QuotaItem: "black-5"}}}, []string{"black-6"}, 1},
		{&App{Name: "white", Units: []Unit{{QuotaItem: "white-9"}}}, []string{"white-10"}, 1},
		{&App{}, []string{"-0"}, 1},
		{&App{Name: "white", Units: []Unit{{Name: "white/0"}}}, []string{"white-0"}, 1},
		{&App{Name: "white", Units: []Unit{{QuotaItem: "white-w"}}}, []string{"white-0"}, 1},
		{&App{Name: "white", Units: []Unit{{QuotaItem: "white-4"}}}, []string{"white-5", "white-6", "white-7"}, 3},
		{&App{Name: "black"}, []string{"black-0", "black-1", "black-2", "black-3"}, 4},
		{&App{Name: "white", Units: []Unit{{QuotaItem: "white-w"}}}, []string{"white-0", "white-1", "white-2"}, 3},
		{&App{Name: "black-white"}, []string{"black-white-0", "black-white-1", "black-white-2"}, 3},
	}
	for _, t := range tests {
		got := generateUnitQuotaItems(t.app, t.n)
		c.Check(got, gocheck.DeepEquals, t.want)
	}
}
<commit_msg>app: added tests for StatusBuilding.<commit_after>// Copyright 2013 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package app

import (
	"github.com/globocom/tsuru/app/bind"
	"github.com/globocom/tsuru/provision"
	"launchpad.net/gocheck"
	"sort"
)

func (s *S) TestUnitGetName(c *gocheck.C) {
	u := Unit{Name: "abcdef", app: &App{Name: "2112"}}
	c.Assert(u.GetName(), gocheck.Equals, "abcdef")
}

func (s *S) TestUnitGetMachine(c *gocheck.C) {
	u := Unit{Machine: 10}
	c.Assert(u.GetMachine(), gocheck.Equals, u.Machine)
}

func (s *S) TestUnitGetStatus(c *gocheck.C) {
	var tests = []struct {
		input    string
		expected provision.Status
	}{
		{"started", provision.StatusStarted},
		{"pending", provision.StatusPending},
		{"building", provision.StatusBuilding},
		{"creating", provision.StatusCreating},
		{"down", provision.StatusDown},
		{"error", provision.StatusError},
		{"installing", provision.StatusInstalling},
		{"creating", provision.StatusCreating},
	}
	for _, test := range tests {
		u := Unit{State: test.input}
		got := u.GetStatus()
		c.Assert(got, gocheck.Equals, test.expected)
	}
}

func (s *S) TestUnitShouldBeABinderUnit(c *gocheck.C) {
	var _ bind.Unit = &Unit{}
}

func (s *S) TestUnitSliceLen(c *gocheck.C) {
	units := UnitSlice{Unit{}, Unit{}}
	c.Assert(units.Len(), gocheck.Equals, 2)
}

func (s *S) TestUnitSliceLess(c *gocheck.C) {
	units := UnitSlice{
		Unit{Name: "a", State: string(provision.StatusError)},
		Unit{Name: "b", State: string(provision.StatusDown)},
		Unit{Name: "c", State: string(provision.StatusPending)},
		Unit{Name: "d", State: string(provision.StatusCreating)},
		Unit{Name: "e", State: string(provision.StatusInstalling)},
		Unit{Name: "f", State: string(provision.StatusBuilding)},
		Unit{Name: "g", State: string(provision.StatusStarted)},
	}
	c.Assert(units.Less(0, 1), gocheck.Equals, true)
	c.Assert(units.Less(1, 2), gocheck.Equals, true)
	c.Assert(units.Less(2, 3), gocheck.Equals, true)
	c.Assert(units.Less(4, 5), gocheck.Equals, true)
	c.Assert(units.Less(5, 6), gocheck.Equals, true)
	c.Assert(units.Less(6, 0), gocheck.Equals, false)
}

func (s *S) TestUnitSliceSwap(c *gocheck.C) {
	units := UnitSlice{
		Unit{Name: "b", State: string(provision.StatusDown)},
		Unit{Name: "c", State: string(provision.StatusPending)},
		Unit{Name: "a", State: string(provision.StatusError)},
		Unit{Name: "d", State: string(provision.StatusCreating)},
		Unit{Name: "e", State: string(provision.StatusInstalling)},
		Unit{Name: "f", State: string(provision.StatusBuilding)},
		Unit{Name: "g", State: string(provision.StatusStarted)},
	}
	units.Swap(0, 2)
	c.Assert(units.Less(0, 2), gocheck.Equals, true)
}

func (s *S) TestUnitSliceSort(c *gocheck.C) {
	units := UnitSlice{
		Unit{Name: "b", State: string(provision.StatusDown)},
		Unit{Name: "c", State: string(provision.StatusPending)},
		Unit{Name: "a", State: string(provision.StatusError)},
		Unit{Name: "d", State: string(provision.StatusCreating)},
		Unit{Name: "e", State: string(provision.StatusInstalling)},
		Unit{Name: "f", State: string(provision.StatusBuilding)},
		Unit{Name: "g", State: string(provision.StatusStarted)},
	}
	c.Assert(sort.IsSorted(units), gocheck.Equals, false)
	sort.Sort(units)
	c.Assert(sort.IsSorted(units), gocheck.Equals, true)
}

func (s *S) TestGenerateUnitQuotaItems(c *gocheck.C) {
	var tests = []struct {
		app  *App
		want []string
		n    int
	}{
		{&App{Name: "black"}, []string{"black-0"}, 1},
		{&App{Name: "black", Units: []Unit{{QuotaItem: "black-1"}, {QuotaItem: "black-5"}}}, []string{"black-6"}, 1},
		{&App{Name: "white", Units: []Unit{{QuotaItem: "white-9"}}}, []string{"white-10"}, 1},
		{&App{}, []string{"-0"}, 1},
		{&App{Name: "white", Units: []Unit{{Name: "white/0"}}}, []string{"white-0"}, 1},
		{&App{Name: "white", Units: []Unit{{QuotaItem: "white-w"}}}, []string{"white-0"}, 1},
		{&App{Name: "white", Units: []Unit{{QuotaItem: "white-4"}}}, []string{"white-5", "white-6", "white-7"}, 3},
		{&App{Name: "black"}, []string{"black-0", "black-1", "black-2", "black-3"}, 4},
		{&App{Name: "white", Units: []Unit{{QuotaItem: "white-w"}}}, []string{"white-0", "white-1", "white-2"}, 3},
		{&App{Name: "black-white"}, []string{"black-white-0", "black-white-1", "black-white-2"}, 3},
	}
	for _, t := range tests {
		got := generateUnitQuotaItems(t.app, t.n)
		c.Check(got, gocheck.DeepEquals, t.want)
	}
}
<|endoftext|>