<commit_before>/*
Copyright 2015 Gravitational, Inc.

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 web

import (
	"encoding/json"
	"fmt"
	"net/http"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/form"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/log"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/roundtrip"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/session"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/julienschmidt/httprouter"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/ttlmap"
	"github.com/gravitational/teleport/lib/reversetunnel"
	"github.com/gravitational/teleport/lib/utils"
)

type MultiSiteHandler struct {
	httprouter.Router
	cfg       MultiSiteConfig
	auth      AuthHandler
	sites     *ttlmap.TtlMap
	templates map[string]*template.Template
}

type MultiSiteConfig struct {
	Tun        reversetunnel.Server
	AssetsDir  string
	AuthAddr   utils.NetAddr
	DomainName string
}

func NewMultiSiteHandler(cfg MultiSiteConfig) (*MultiSiteHandler, error) {
	lauth, err := NewLocalAuth(cfg.DomainName, []utils.NetAddr{cfg.AuthAddr})
	if err != nil {
		return nil, err
	}

	sites, err := ttlmap.NewMap(1024)
	if err != nil {
		return nil, err
	}

	h := &MultiSiteHandler{
		cfg:   cfg,
		auth:  lauth,
		sites: sites,
	}

	h.initTemplates(cfg.AssetsDir)

	// WEB views
	h.GET("/web/login", h.login)
	h.GET("/web/loginerror", h.loginError)
	h.GET("/web/logout", h.logout)
	h.POST("/web/auth", h.authForm)

	h.GET("/", h.needsAuth(h.sitesIndex))
	h.GET("/web/sites", h.needsAuth(h.sitesIndex))

	// For ssh proxy
	h.POST("/sshlogin", h.loginSSHProxy)

	// Forward all requests to site handler
	sh := h.needsAuth(h.siteHandler)
	h.GET("/tun/:site/*path", sh)
	h.PUT("/tun/:site/*path", sh)
	h.POST("/tun/:site/*path", sh)
	h.DELETE("/tun/:site/*path", sh)

	// API views
	h.GET("/api/sites", h.needsAuth(h.handleGetSites))

	// Static assets
	h.Handler("GET", "/static/*filepath",
		http.FileServer(http.Dir(filepath.Join(cfg.AssetsDir, "assets"))))
	return h, nil
}

func (s *MultiSiteHandler) initTemplates(baseDir string) {
	tpls := []tpl{
		tpl{name: "login", include: []string{"assets/static/tpl/login.tpl", "assets/static/tpl/base.tpl"}},
		tpl{name: "sites", include: []string{"assets/static/tpl/sites.tpl", "assets/static/tpl/base.tpl"}},
		tpl{name: "site-servers", include: []string{"assets/static/tpl/site/servers.tpl", "assets/static/tpl/base.tpl"}},
		tpl{name: "site-events", include: []string{"assets/static/tpl/site/events.tpl", "assets/static/tpl/base.tpl"}},
	}
	s.templates = make(map[string]*template.Template)
	for _, t := range tpls {
		tpl := template.New(t.name)
		for _, i := range t.include {
			template.Must(tpl.ParseFiles(filepath.Join(baseDir, i)))
		}
		s.templates[t.name] = tpl
	}
}

func (h *MultiSiteHandler) String() string {
	return fmt.Sprintf("multi site")
}

func (h *MultiSiteHandler) login(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	h.executeTemplate(w, "login", nil) ///map[string]interface{}{"ErrorString": ""})
}

func (h *MultiSiteHandler) loginError(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	h.executeTemplate(w, "login", map[string]interface{}{"ErrorString": "Wrong username or password or hotp token"})
}

func (h *MultiSiteHandler) logout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	if err := session.ClearSession(w, h.cfg.DomainName); err != nil {
		log.Errorf("failed to clear session: %v", err)
		replyErr(w, http.StatusInternalServerError, fmt.Errorf("failed to logout"))
		return
	}
	http.Redirect(w, r, "/web/login", http.StatusFound)
}

func (h *MultiSiteHandler) authForm(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var user, pass, hotpToken string

	err := form.Parse(r,
		form.String("username", &user, form.Required()),
		form.String("password", &pass, form.Required()),
		form.String("hotpToken", &hotpToken, form.Required()),
	)

	if err != nil {
		replyErr(w, http.StatusBadRequest, err)
		return
	}
	sid, err := h.auth.Auth(user, pass, hotpToken)
	if err != nil {
		log.Warningf("auth error: %v", err)
		http.Redirect(w, r, "/web/loginerror", http.StatusFound)
		return
	}
	if err := session.SetSession(w, h.cfg.DomainName, user, sid); err != nil {
		replyErr(w, http.StatusInternalServerError, err)
		return
	}

	http.Redirect(w, r, "/", http.StatusFound)
}

func (h *MultiSiteHandler) loginSSHProxy(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var credJSON string

	err := form.Parse(r,
		form.String("credentials", &credJSON, form.Required()),
	)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(trace.Wrap(err).Error()))
		return
	}

	var cred SSHLoginCredentials
	if err := json.Unmarshal([]byte(credJSON), &cred); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(trace.Wrap(err).Error()))
		return
	}

	cert, err := h.auth.GetCertificate(cred)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(trace.Wrap(err).Error()))
		return
	}
	w.Write(cert)
}

func (s *MultiSiteHandler) siteEvents(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) error {
	s.executeTemplate(w, "site-events", map[string]interface{}{"SiteName": p[0].Value})
	return nil
}

func (s *MultiSiteHandler) siteServers(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) error {
	s.executeTemplate(w, "site-servers", map[string]interface{}{"SiteName": p[0].Value})
	return nil
}

func (s *MultiSiteHandler) sitesIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) error {
	s.executeTemplate(w, "sites", nil)
	return nil
}

func (s *MultiSiteHandler) siteHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) error {
	siteName := p[0].Value
	prefix := fmt.Sprintf("/tun/%v", siteName)
	i, ok := s.sites.Get(siteName)
	if !ok {
		tauth, err := NewTunAuth(s.auth, s.cfg.Tun, siteName)
		if err != nil {
			return err
		}
		i = NewSiteHandler(SiteHandlerConfig{
			Auth:      tauth,
			AssetsDir: s.cfg.AssetsDir,
			URLPrefix: prefix,
			NavSections: []NavSection{
				NavSection{
					Title: "Back to Portal",
					Icon:  "fa fa-arrow-circle-left",
					URL:   "/",
				},
			},
		})
		if err := s.sites.Set(siteName, i, 90); err != nil {
			return err
		}
	}
	sh := i.(http.Handler)
	r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix)
	r.RequestURI = r.URL.String()
	log.Infof("siteHandler: %v %v", r.Method, r.URL)
	sh.ServeHTTP(w, r)
	return nil
}

func (h *MultiSiteHandler) handleGetSites(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) error {
	roundtrip.ReplyJSON(w, http.StatusOK, sitesResponse(h.cfg.Tun.GetSites()))
	return nil
}

func (h *MultiSiteHandler) needsAuth(fn authHandle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		cookie, err := r.Cookie("session")
		if err != nil {
			log.Infof("getting cookie: %v", err)
			http.Redirect(w, r, "/web/login", http.StatusFound)
			return
		}
		d, err := session.DecodeCookie(cookie.Value)
		if err != nil {
			log.Warningf("failed to decode cookie '%v', err: %v", cookie.Value, err)
			http.Redirect(w, r, "/web/login", http.StatusFound)
			return
		}
		ctx, err := h.auth.ValidateSession(d.User, d.SID)
		if err != nil {
			log.Warningf("failed to validate session: %v", err)
			http.Redirect(w, r, "/web/login", http.StatusFound)
			return
		}
		if err := fn(w, r, p, ctx); err != nil {
			log.Errorf("error in handler: %v", err)
			roundtrip.ReplyJSON(
				w, http.StatusInternalServerError, err.Error())
			return
		}
	}
}

func (h *MultiSiteHandler) executeTemplate(w http.ResponseWriter, name string, data interface{}) {
	tpl, ok := h.templates[name]
	if !ok {
		replyErr(w, http.StatusInternalServerError, fmt.Errorf("template '%v' not found", name))
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := tpl.ExecuteTemplate(w, "base", data); err != nil {
		log.Errorf("Execute template: %v", err)
		replyErr(w, http.StatusInternalServerError, fmt.Errorf("internal render error"))
	}
}

type Server struct {
	http.Server
}

func New(addr utils.NetAddr, cfg MultiSiteConfig) (*Server, error) {
	h, err := NewMultiSiteHandler(cfg)
	if err != nil {
		return nil, err
	}
	srv := &Server{}
	srv.Server.Addr = addr.Addr
	srv.Server.Handler = h
	return srv, nil
}

type site struct {
	Name          string    `json:"name"`
	LastConnected time.Time `json:"last_connected"`
	Status        string    `json:"status"`
}

func sitesResponse(rs []reversetunnel.RemoteSite) []site {
	out := make([]site, len(rs))
	for i := range rs {
		out[i] = site{
			Name:          rs[i].GetName(),
			LastConnected: rs[i].GetLastConnected(),
			Status:        rs[i].GetStatus(),
		}
	}
	return out
}

type authHandle func(http.ResponseWriter, *http.Request, httprouter.Params, Context) error
<commit_msg>cleanup<commit_after>/*
Copyright 2015 Gravitational, Inc.

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 web

import (
	"encoding/json"
	"fmt"
	"net/http"
	"path/filepath"
	"strings"
	"text/template"
	"time"

	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/form"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/log"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/roundtrip"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/session"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/julienschmidt/httprouter"
	"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/ttlmap"
	"github.com/gravitational/teleport/lib/reversetunnel"
	"github.com/gravitational/teleport/lib/utils"
)

type MultiSiteHandler struct {
	httprouter.Router
	cfg       MultiSiteConfig
	auth      AuthHandler
	sites     *ttlmap.TtlMap
	templates map[string]*template.Template
}

type MultiSiteConfig struct {
	Tun        reversetunnel.Server
	AssetsDir  string
	AuthAddr   utils.NetAddr
	DomainName string
}

func NewMultiSiteHandler(cfg MultiSiteConfig) (*MultiSiteHandler, error) {
	lauth, err := NewLocalAuth(cfg.DomainName, []utils.NetAddr{cfg.AuthAddr})
	if err != nil {
		return nil, err
	}

	sites, err := ttlmap.NewMap(1024)
	if err != nil {
		return nil, err
	}

	h := &MultiSiteHandler{
		cfg:   cfg,
		auth:  lauth,
		sites: sites,
	}

	h.initTemplates(cfg.AssetsDir)

	// WEB views
	h.GET("/web/login", h.login)
	h.GET("/web/loginerror", h.loginError)
	h.GET("/web/logout", h.logout)
	h.POST("/web/auth", h.authForm)

	h.GET("/", h.needsAuth(h.sitesIndex))
	h.GET("/web/sites", h.needsAuth(h.sitesIndex))

	// For ssh proxy
	h.POST("/sshlogin", h.loginSSHProxy)

	// Forward all requests to site handler
	sh := h.needsAuth(h.siteHandler)
	h.GET("/tun/:site/*path", sh)
	h.PUT("/tun/:site/*path", sh)
	h.POST("/tun/:site/*path", sh)
	h.DELETE("/tun/:site/*path", sh)

	// API views
	h.GET("/api/sites", h.needsAuth(h.handleGetSites))

	// Static assets
	h.Handler("GET", "/static/*filepath",
		http.FileServer(http.Dir(filepath.Join(cfg.AssetsDir, "assets"))))
	return h, nil
}

func (s *MultiSiteHandler) initTemplates(baseDir string) {
	tpls := []tpl{
		tpl{name: "login", include: []string{"assets/static/tpl/login.tpl", "assets/static/tpl/base.tpl"}},
		tpl{name: "sites", include: []string{"assets/static/tpl/sites.tpl", "assets/static/tpl/base.tpl"}},
		tpl{name: "site-servers", include: []string{"assets/static/tpl/site/servers.tpl", "assets/static/tpl/base.tpl"}},
		tpl{name: "site-events", include: []string{"assets/static/tpl/site/events.tpl", "assets/static/tpl/base.tpl"}},
	}
	s.templates = make(map[string]*template.Template)
	for _, t := range tpls {
		tpl := template.New(t.name)
		for _, i := range t.include {
			template.Must(tpl.ParseFiles(filepath.Join(baseDir, i)))
		}
		s.templates[t.name] = tpl
	}
}

func (h *MultiSiteHandler) String() string {
	return fmt.Sprintf("multi site")
}

func (h *MultiSiteHandler) login(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	h.executeTemplate(w, "login", nil)
}

func (h *MultiSiteHandler) loginError(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	h.executeTemplate(w, "login", map[string]interface{}{"ErrorString": "Wrong username or password or hotp token"})
}

func (h *MultiSiteHandler) logout(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	if err := session.ClearSession(w, h.cfg.DomainName); err != nil {
		log.Errorf("failed to clear session: %v", err)
		replyErr(w, http.StatusInternalServerError, fmt.Errorf("failed to logout"))
		return
	}
	http.Redirect(w, r, "/web/login", http.StatusFound)
}

func (h *MultiSiteHandler) authForm(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var user, pass, hotpToken string

	err := form.Parse(r,
		form.String("username", &user, form.Required()),
		form.String("password", &pass, form.Required()),
		form.String("hotpToken", &hotpToken, form.Required()),
	)

	if err != nil {
		replyErr(w, http.StatusBadRequest, err)
		return
	}
	sid, err := h.auth.Auth(user, pass, hotpToken)
	if err != nil {
		log.Warningf("auth error: %v", err)
		http.Redirect(w, r, "/web/loginerror", http.StatusFound)
		return
	}
	if err := session.SetSession(w, h.cfg.DomainName, user, sid); err != nil {
		replyErr(w, http.StatusInternalServerError, err)
		return
	}

	http.Redirect(w, r, "/", http.StatusFound)
}

func (h *MultiSiteHandler) loginSSHProxy(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
	var credJSON string

	err := form.Parse(r,
		form.String("credentials", &credJSON, form.Required()),
	)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(trace.Wrap(err).Error()))
		return
	}

	var cred SSHLoginCredentials
	if err := json.Unmarshal([]byte(credJSON), &cred); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(trace.Wrap(err).Error()))
		return
	}

	cert, err := h.auth.GetCertificate(cred)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(trace.Wrap(err).Error()))
		return
	}
	w.Write(cert)
}

func (s *MultiSiteHandler) siteEvents(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) error {
	s.executeTemplate(w, "site-events", map[string]interface{}{"SiteName": p[0].Value})
	return nil
}

func (s *MultiSiteHandler) siteServers(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) error {
	s.executeTemplate(w, "site-servers", map[string]interface{}{"SiteName": p[0].Value})
	return nil
}

func (s *MultiSiteHandler) sitesIndex(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) error {
	s.executeTemplate(w, "sites", nil)
	return nil
}

func (s *MultiSiteHandler) siteHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params, c Context) error {
	siteName := p[0].Value
	prefix := fmt.Sprintf("/tun/%v", siteName)
	i, ok := s.sites.Get(siteName)
	if !ok {
		tauth, err := NewTunAuth(s.auth, s.cfg.Tun, siteName)
		if err != nil {
			return err
		}
		i = NewSiteHandler(SiteHandlerConfig{
			Auth:      tauth,
			AssetsDir: s.cfg.AssetsDir,
			URLPrefix: prefix,
			NavSections: []NavSection{
				NavSection{
					Title: "Back to Portal",
					Icon:  "fa fa-arrow-circle-left",
					URL:   "/",
				},
			},
		})
		if err := s.sites.Set(siteName, i, 90); err != nil {
			return err
		}
	}
	sh := i.(http.Handler)
	r.URL.Path = strings.TrimPrefix(r.URL.Path, prefix)
	r.RequestURI = r.URL.String()
	log.Infof("siteHandler: %v %v", r.Method, r.URL)
	sh.ServeHTTP(w, r)
	return nil
}

func (h *MultiSiteHandler) handleGetSites(w http.ResponseWriter, r *http.Request, _ httprouter.Params, c Context) error {
	roundtrip.ReplyJSON(w, http.StatusOK, sitesResponse(h.cfg.Tun.GetSites()))
	return nil
}

func (h *MultiSiteHandler) needsAuth(fn authHandle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
		cookie, err := r.Cookie("session")
		if err != nil {
			log.Infof("getting cookie: %v", err)
			http.Redirect(w, r, "/web/login", http.StatusFound)
			return
		}
		d, err := session.DecodeCookie(cookie.Value)
		if err != nil {
			log.Warningf("failed to decode cookie '%v', err: %v", cookie.Value, err)
			http.Redirect(w, r, "/web/login", http.StatusFound)
			return
		}
		ctx, err := h.auth.ValidateSession(d.User, d.SID)
		if err != nil {
			log.Warningf("failed to validate session: %v", err)
			http.Redirect(w, r, "/web/login", http.StatusFound)
			return
		}
		if err := fn(w, r, p, ctx); err != nil {
			log.Errorf("error in handler: %v", err)
			roundtrip.ReplyJSON(
				w, http.StatusInternalServerError, err.Error())
			return
		}
	}
}

func (h *MultiSiteHandler) executeTemplate(w http.ResponseWriter, name string, data interface{}) {
	tpl, ok := h.templates[name]
	if !ok {
		replyErr(w, http.StatusInternalServerError, fmt.Errorf("template '%v' not found", name))
		return
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := tpl.ExecuteTemplate(w, "base", data); err != nil {
		log.Errorf("Execute template: %v", err)
		replyErr(w, http.StatusInternalServerError, fmt.Errorf("internal render error"))
	}
}

type Server struct {
	http.Server
}

func New(addr utils.NetAddr, cfg MultiSiteConfig) (*Server, error) {
	h, err := NewMultiSiteHandler(cfg)
	if err != nil {
		return nil, err
	}
	srv := &Server{}
	srv.Server.Addr = addr.Addr
	srv.Server.Handler = h
	return srv, nil
}

type site struct {
	Name          string    `json:"name"`
	LastConnected time.Time `json:"last_connected"`
	Status        string    `json:"status"`
}

func sitesResponse(rs []reversetunnel.RemoteSite) []site {
	out := make([]site, len(rs))
	for i := range rs {
		out[i] = site{
			Name:          rs[i].GetName(),
			LastConnected: rs[i].GetLastConnected(),
			Status:        rs[i].GetStatus(),
		}
	}
	return out
}

type authHandle func(http.ResponseWriter, *http.Request, httprouter.Params, Context) error
<|endoftext|>


<commit_before>package gapi

import (
	"bytes"
	"encoding/json"
	"fmt"
	"time"
)

// LibraryPanelMetaUser represents the Grafana library panel createdBy and updatedBy fields
type LibraryPanelMetaUser struct {
	ID        int64  `json:"id"`
	Name      string `json:"name"`
	AvatarURL string `json:"folderId"`
}

// LibraryPanelMeta represents Grafana library panel metadata.
type LibraryPanelMeta struct {
	FolderName          string               `json:"folderName,,omitempty"`
	FolderUID           string               `json:"folderUid,omitempty"`
	ConnectedDashboards int64                `json:"connectedDashboards,omitempty"`
	Created             time.Time            `json:"created,omitempty"`
	Updated             time.Time            `json:"updated,omitempty"`
	CreatedBy           LibraryPanelMetaUser `json:"createdBy,omitempty"`
	UpdatedBy           LibraryPanelMetaUser `json:"updatedBy,omitempty"`
}

// LibraryPanel represents a Grafana library panel.
type LibraryPanel struct {
	Folder      int64                  `json:"folderId,omitempty"`
	Name        string                 `json:"name"`
	Model       map[string]interface{} `json:"model"`
	Type        string                 `json:"type,omitempty"`
	Description string                 `json:"description,omitempty"`
	ID          int64                  `json:"id,omitempty"`
	Kind        int64                  `json:"kind,omitempty"`
	OrgID       int64                  `json:"orgId,omitempty"`
	UID         string                 `json:"uid,omitempty"`
	Version     int64                  `json:"version,omitempty"`
	Meta        LibraryPanelMeta       `json:"meta,omitempty"`
}

// LibraryPanelCreateResponse represents the Grafana API response to creating or saving a library panel.
type LibraryPanelCreateResponse struct {
	Result LibraryPanel `json:"result"`
}

// LibraryPanelGetAllResponse represents the Grafana API response to getting all library panels.
type LibraryPanelGetAllResponse struct {
	TotalCount int64          `json:"totalCount"`
	Page       int64          `json:"page"`
	PerPage    int64          `json:"perPage"`
	Elements   []LibraryPanel `json:"elements"`
}

// LibraryPanelDeleteResponse represents the Grafana API response to deleting a library panel.
type LibraryPanelDeleteResponse struct {
	Message string `json:"message"`
	ID      int64  `json:"id,omitempty"`
}

// LibraryPanelConnection represents a Grafana connection between a library panel and a dashboard.
type LibraryPanelConnection struct {
	ID          int64                `json:"id"`
	Kind        int64                `json:"kind"`
	PanelID     int64                `json:"elementId"`
	DashboardID int64                `json:"connectionId"`
	Created     time.Time            `json:"created"`
	CreatedBy   LibraryPanelMetaUser `json:"createdBy"`
}

// NewLibraryPanel creates a new Grafana library panel.
func (c *Client) NewLibraryPanel(panel LibraryPanel) (*LibraryPanel, error) {
	panel.Kind = int64(1)
	data, err := json.Marshal(panel)
	if err != nil {
		return nil, err
	}

	resp := &LibraryPanelCreateResponse{}
	err = c.request("POST", "/api/library-elements", nil, bytes.NewBuffer(data), &resp)
	if err != nil {
		return nil, err
	}

	return &resp.Result, err
}

// Dashboards fetches and returns all dashboards.
func (c *Client) LibraryPanels() ([]LibraryPanel, error) {
	resp := &struct {
		Result LibraryPanelGetAllResponse `json:"result"`
	}{}
	err := c.request("GET", "/api/library-elements", nil, nil, &resp)
	if err != nil {
		return nil, err
	}

	return resp.Result.Elements, err
}

// LibraryPanelByUID gets a library panel by UID.
func (c *Client) LibraryPanelByUID(uid string) (*LibraryPanel, error) {
	resp := &LibraryPanelCreateResponse{}
	path := fmt.Sprintf("/api/library-elements/%s", uid)

	err := c.request("GET", path, nil, nil, &resp)
	if err != nil {
		return nil, err
	}

	return &resp.Result, err
}

// LibraryPanelByName gets library panels by name.
func (c *Client) LibraryPanelByName(name string) (*LibraryPanel, error) {
	resp := &struct {
		Result []LibraryPanel `json:"result"`
	}{}
	path := fmt.Sprintf("/api/library-elements/name/%s", name)

	err := c.request("GET", path, nil, nil, &resp)
	if err != nil {
		return nil, err
	}

	if len(resp.Result) != 1 {
		return nil, fmt.Errorf("error: expected 1 panel from GET library panel by name, got: %v", resp.Result)
	}

	return &(resp.Result[0]), err
}

// PatchLibraryPanel updates one or more properties of an existing panel that matches the specified UID.
func (c *Client) PatchLibraryPanel(uid string, panel LibraryPanel) (*LibraryPanel, error) {
	path := fmt.Sprintf("/api/library-elements/%s", uid)
	panel.Kind = int64(1)

	// if Version not specified, get current version from API
	if panel.Version == int64(0) {
		remotePanel, err := c.LibraryPanelByUID(panel.UID)
		if err != nil {
			return nil, err
		}
		panel.Version = remotePanel.Version
	}

	data, err := json.Marshal(panel)
	if err != nil {
		return nil, err
	}

	resp := &LibraryPanelCreateResponse{}
	err = c.request("PATCH", path, nil, bytes.NewBuffer(data), &resp)
	if err != nil {
		return nil, err
	}

	return &resp.Result, err
}

// DeleteLibraryPanel deletes a panel by UID.
func (c *Client) DeleteLibraryPanel(uid string) (*LibraryPanelDeleteResponse, error) {
	path := fmt.Sprintf("/api/library-elements/%s", uid)

	resp := &LibraryPanelDeleteResponse{}
	err := c.request("DELETE", path, nil, bytes.NewBuffer(nil), &resp)
	if err != nil {
		return nil, err
	}

	return resp, err
}

// LibraryPanelConnections gets library panel connections by UID.
func (c *Client) LibraryPanelConnections(uid string) (*[]LibraryPanelConnection, error) {
	path := fmt.Sprintf("/api/library-elements/%s/connections", uid)

	resp := struct {
		Result []LibraryPanelConnection `json:"result"`
	}{}

	err := c.request("GET", path, nil, bytes.NewBuffer(nil), &resp)
	if err != nil {
		return nil, err
	}

	return &resp.Result, err
}

// LibraryPanelConnectedDashboards gets Dashboards using this Library Panel.
func (c *Client) LibraryPanelConnectedDashboards(uid string) ([]FolderDashboardSearchResponse, error) {
	connections, err := c.LibraryPanelConnections(uid)
	if err != nil {
		return nil, err
	}

	var dashboardIds []int64
	for _, connection := range *connections {
		dashboardIds = append(dashboardIds, connection.DashboardID)
	}

	return c.DashboardsByIDs(dashboardIds)
}
<commit_msg>remove unneeded parentheses<commit_after>package gapi

import (
	"bytes"
	"encoding/json"
	"fmt"
	"time"
)

// LibraryPanelMetaUser represents the Grafana library panel createdBy and updatedBy fields
type LibraryPanelMetaUser struct {
	ID        int64  `json:"id"`
	Name      string `json:"name"`
	AvatarURL string `json:"folderId"`
}

// LibraryPanelMeta represents Grafana library panel metadata.
type LibraryPanelMeta struct {
	FolderName          string               `json:"folderName,,omitempty"`
	FolderUID           string               `json:"folderUid,omitempty"`
	ConnectedDashboards int64                `json:"connectedDashboards,omitempty"`
	Created             time.Time            `json:"created,omitempty"`
	Updated             time.Time            `json:"updated,omitempty"`
	CreatedBy           LibraryPanelMetaUser `json:"createdBy,omitempty"`
	UpdatedBy           LibraryPanelMetaUser `json:"updatedBy,omitempty"`
}

// LibraryPanel represents a Grafana library panel.
type LibraryPanel struct {
	Folder      int64                  `json:"folderId,omitempty"`
	Name        string                 `json:"name"`
	Model       map[string]interface{} `json:"model"`
	Type        string                 `json:"type,omitempty"`
	Description string                 `json:"description,omitempty"`
	ID          int64                  `json:"id,omitempty"`
	Kind        int64                  `json:"kind,omitempty"`
	OrgID       int64                  `json:"orgId,omitempty"`
	UID         string                 `json:"uid,omitempty"`
	Version     int64                  `json:"version,omitempty"`
	Meta        LibraryPanelMeta       `json:"meta,omitempty"`
}

// LibraryPanelCreateResponse represents the Grafana API response to creating or saving a library panel.
type LibraryPanelCreateResponse struct {
	Result LibraryPanel `json:"result"`
}

// LibraryPanelGetAllResponse represents the Grafana API response to getting all library panels.
type LibraryPanelGetAllResponse struct {
	TotalCount int64          `json:"totalCount"`
	Page       int64          `json:"page"`
	PerPage    int64          `json:"perPage"`
	Elements   []LibraryPanel `json:"elements"`
}

// LibraryPanelDeleteResponse represents the Grafana API response to deleting a library panel.
type LibraryPanelDeleteResponse struct {
	Message string `json:"message"`
	ID      int64  `json:"id,omitempty"`
}

// LibraryPanelConnection represents a Grafana connection between a library panel and a dashboard.
type LibraryPanelConnection struct {
	ID          int64                `json:"id"`
	Kind        int64                `json:"kind"`
	PanelID     int64                `json:"elementId"`
	DashboardID int64                `json:"connectionId"`
	Created     time.Time            `json:"created"`
	CreatedBy   LibraryPanelMetaUser `json:"createdBy"`
}

// NewLibraryPanel creates a new Grafana library panel.
func (c *Client) NewLibraryPanel(panel LibraryPanel) (*LibraryPanel, error) {
	panel.Kind = int64(1)
	data, err := json.Marshal(panel)
	if err != nil {
		return nil, err
	}

	resp := &LibraryPanelCreateResponse{}
	err = c.request("POST", "/api/library-elements", nil, bytes.NewBuffer(data), &resp)
	if err != nil {
		return nil, err
	}

	return &resp.Result, err
}

// Dashboards fetches and returns all dashboards.
func (c *Client) LibraryPanels() ([]LibraryPanel, error) {
	resp := &struct {
		Result LibraryPanelGetAllResponse `json:"result"`
	}{}
	err := c.request("GET", "/api/library-elements", nil, nil, &resp)
	if err != nil {
		return nil, err
	}

	return resp.Result.Elements, err
}

// LibraryPanelByUID gets a library panel by UID.
func (c *Client) LibraryPanelByUID(uid string) (*LibraryPanel, error) {
	resp := &LibraryPanelCreateResponse{}
	path := fmt.Sprintf("/api/library-elements/%s", uid)

	err := c.request("GET", path, nil, nil, &resp)
	if err != nil {
		return nil, err
	}

	return &resp.Result, err
}

// LibraryPanelByName gets library panels by name.
func (c *Client) LibraryPanelByName(name string) (*LibraryPanel, error) {
	resp := &struct {
		Result []LibraryPanel `json:"result"`
	}{}
	path := fmt.Sprintf("/api/library-elements/name/%s", name)

	err := c.request("GET", path, nil, nil, &resp)
	if err != nil {
		return nil, err
	}

	if len(resp.Result) != 1 {
		return nil, fmt.Errorf("error: expected 1 panel from GET library panel by name, got: %v", resp.Result)
	}

	return &resp.Result[0], err
}

// PatchLibraryPanel updates one or more properties of an existing panel that matches the specified UID.
func (c *Client) PatchLibraryPanel(uid string, panel LibraryPanel) (*LibraryPanel, error) {
	path := fmt.Sprintf("/api/library-elements/%s", uid)
	panel.Kind = int64(1)

	// if Version not specified, get current version from API
	if panel.Version == int64(0) {
		remotePanel, err := c.LibraryPanelByUID(panel.UID)
		if err != nil {
			return nil, err
		}
		panel.Version = remotePanel.Version
	}

	data, err := json.Marshal(panel)
	if err != nil {
		return nil, err
	}

	resp := &LibraryPanelCreateResponse{}
	err = c.request("PATCH", path, nil, bytes.NewBuffer(data), &resp)
	if err != nil {
		return nil, err
	}

	return &resp.Result, err
}

// DeleteLibraryPanel deletes a panel by UID.
func (c *Client) DeleteLibraryPanel(uid string) (*LibraryPanelDeleteResponse, error) {
	path := fmt.Sprintf("/api/library-elements/%s", uid)

	resp := &LibraryPanelDeleteResponse{}
	err := c.request("DELETE", path, nil, bytes.NewBuffer(nil), &resp)
	if err != nil {
		return nil, err
	}

	return resp, err
}

// LibraryPanelConnections gets library panel connections by UID.
func (c *Client) LibraryPanelConnections(uid string) (*[]LibraryPanelConnection, error) {
	path := fmt.Sprintf("/api/library-elements/%s/connections", uid)

	resp := struct {
		Result []LibraryPanelConnection `json:"result"`
	}{}

	err := c.request("GET", path, nil, bytes.NewBuffer(nil), &resp)
	if err != nil {
		return nil, err
	}

	return &resp.Result, err
}

// LibraryPanelConnectedDashboards gets Dashboards using this Library Panel.
func (c *Client) LibraryPanelConnectedDashboards(uid string) ([]FolderDashboardSearchResponse, error) {
	connections, err := c.LibraryPanelConnections(uid)
	if err != nil {
		return nil, err
	}

	var dashboardIds []int64
	for _, connection := range *connections {
		dashboardIds = append(dashboardIds, connection.DashboardID)
	}

	return c.DashboardsByIDs(dashboardIds)
}
<|endoftext|>


<commit_before>// Copyright 2018 The Go 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 lsp

import (
	"bytes"
	"context"
	"fmt"
	"go/ast"
	"go/token"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"golang.org/x/mod/modfile"
	"golang.org/x/tools/internal/event"
	"golang.org/x/tools/internal/lsp/debug/tag"
	"golang.org/x/tools/internal/lsp/protocol"
	"golang.org/x/tools/internal/lsp/source"
	"golang.org/x/tools/internal/span"
)

func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) (links []protocol.DocumentLink, err error) {
	snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
	defer release()
	if !ok {
		return nil, err
	}
	switch snapshot.View().FileKind(fh) {
	case source.Mod:
		links, err = modLinks(ctx, snapshot, fh)
	case source.Go:
		links, err = goLinks(ctx, snapshot, fh)
	}
	// Don't return errors for document links.
	if err != nil {
		event.Error(ctx, "failed to compute document links", err, tag.URI.Of(fh.URI()))
		return nil, nil
	}
	return links, nil
}

func modLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) {
	pm, err := snapshot.ParseMod(ctx, fh)
	if err != nil {
		return nil, err
	}
	var links []protocol.DocumentLink
	for _, req := range pm.File.Require {
		if req.Syntax == nil {
			continue
		}
		// See golang/go#36998: don't link to modules matching GOPRIVATE.
		if snapshot.View().IsGoPrivatePath(req.Mod.Path) {
			continue
		}
		dep := []byte(req.Mod.Path)
		s, e := req.Syntax.Start.Byte, req.Syntax.End.Byte
		i := bytes.Index(pm.Mapper.Content[s:e], dep)
		if i == -1 {
			continue
		}
		// Shift the start position to the location of the
		// dependency within the require statement.
		start, end := token.Pos(s+i), token.Pos(s+i+len(dep))
		target := source.BuildLink(snapshot.View().Options().LinkTarget, "mod/"+req.Mod.String(), "")
		l, err := toProtocolLink(snapshot, pm.Mapper, target, start, end, source.Mod)
		if err != nil {
			return nil, err
		}
		links = append(links, l)
	}
	// TODO(ridersofrohan): handle links for replace and exclude directives.
	if syntax := pm.File.Syntax; syntax == nil {
		return links, nil
	}
	// Get all the links that are contained in the comments of the file.
	for _, expr := range pm.File.Syntax.Stmt {
		comments := expr.Comment()
		if comments == nil {
			continue
		}
		for _, section := range [][]modfile.Comment{comments.Before, comments.Suffix, comments.After} {
			for _, comment := range section {
				l, err := findLinksInString(ctx, snapshot, comment.Token, token.Pos(comment.Start.Byte), pm.Mapper, source.Mod)
				if err != nil {
					return nil, err
				}
				links = append(links, l...)
			}
		}
	}
	return links, nil
}

func goLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) {
	view := snapshot.View()
	// We don't actually need type information, so any typecheck mode is fine.
	pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckWorkspace, source.WidestPackage)
	if err != nil {
		return nil, err
	}
	pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull)
	if err != nil {
		return nil, err
	}
	var imports []*ast.ImportSpec
	var str []*ast.BasicLit
	ast.Inspect(pgf.File, func(node ast.Node) bool {
		switch n := node.(type) {
		case *ast.ImportSpec:
			imports = append(imports, n)
			return false
		case *ast.BasicLit:
			// Look for links in string literals.
			if n.Kind == token.STRING {
				str = append(str, n)
			}
			return false
		}
		return true
	})
	var links []protocol.DocumentLink
	// For import specs, provide a link to a documentation website, like
	// https://pkg.go.dev.
	if view.Options().ImportShortcut.ShowLinks() {
		for _, imp := range imports {
			target, err := strconv.Unquote(imp.Path.Value)
			if err != nil {
				continue
			}
			// See golang/go#36998: don't link to modules matching GOPRIVATE.
			if view.IsGoPrivatePath(target) {
				continue
			}
			if mod, version, ok := moduleAtVersion(ctx, snapshot, target, pkg); ok && strings.ToLower(view.Options().LinkTarget) == "pkg.go.dev" {
				target = strings.Replace(target, mod, mod+"@"+version, 1)
			}
			// Account for the quotation marks in the positions.
			start := imp.Path.Pos() + 1
			end := imp.Path.End() - 1
			target = source.BuildLink(view.Options().LinkTarget, target, "")
			l, err := toProtocolLink(snapshot, pgf.Mapper, target, start, end, source.Go)
			if err != nil {
				return nil, err
			}
			links = append(links, l)
		}
	}
	for _, s := range str {
		l, err := findLinksInString(ctx, snapshot, s.Value, s.Pos(), pgf.Mapper, source.Go)
		if err != nil {
			return nil, err
		}
		links = append(links, l...)
	}
	for _, commentGroup := range pgf.File.Comments {
		for _, comment := range commentGroup.List {
			l, err := findLinksInString(ctx, snapshot, comment.Text, comment.Pos(), pgf.Mapper, source.Go)
			if err != nil {
				return nil, err
			}
			links = append(links, l...)
		}
	}
	return links, nil
}

func moduleAtVersion(ctx context.Context, snapshot source.Snapshot, target string, pkg source.Package) (string, string, bool) {
	impPkg, err := pkg.GetImport(target)
	if err != nil {
		return "", "", false
	}
	if impPkg.Version() == nil {
		return "", "", false
	}
	version, modpath := impPkg.Version().Version, impPkg.Version().Path
	if modpath == "" || version == "" {
		return "", "", false
	}
	return modpath, version, true
}

func findLinksInString(ctx context.Context, snapshot source.Snapshot, src string, pos token.Pos, m *protocol.ColumnMapper, fileKind source.FileKind) ([]protocol.DocumentLink, error) {
	var links []protocol.DocumentLink
	for _, index := range snapshot.View().Options().URLRegexp.FindAllIndex([]byte(src), -1) {
		start, end := index[0], index[1]
		startPos := token.Pos(int(pos) + start)
		endPos := token.Pos(int(pos) + end)
		link := src[start:end]
		linkURL, err := url.Parse(link)
		// Fallback: Linkify IP addresses as suggested in golang/go#18824.
		if err != nil {
			linkURL, err = url.Parse("//" + link)
			// Not all potential links will be valid, so don't return this error.
			if err != nil {
				continue
			}
		}
		// If the URL has no scheme, use https.
		if linkURL.Scheme == "" {
			linkURL.Scheme = "https"
		}
		l, err := toProtocolLink(snapshot, m, linkURL.String(), startPos, endPos, fileKind)
		if err != nil {
			return nil, err
		}
		links = append(links, l)
	}
	// Handle golang/go#1234-style links.
	r := getIssueRegexp()
	for _, index := range r.FindAllIndex([]byte(src), -1) {
		start, end := index[0], index[1]
		startPos := token.Pos(int(pos) + start)
		endPos := token.Pos(int(pos) + end)
		matches := r.FindStringSubmatch(src)
		if len(matches) < 4 {
			continue
		}
		org, repo, number := matches[1], matches[2], matches[3]
		target := fmt.Sprintf("https://github.com/%s/%s/issues/%s", org, repo, number)
		l, err := toProtocolLink(snapshot, m, target, startPos, endPos, fileKind)
		if err != nil {
			return nil, err
		}
		links = append(links, l)
	}
	return links, nil
}

func getIssueRegexp() *regexp.Regexp {
	once.Do(func() {
		issueRegexp = regexp.MustCompile(`(\w+)/([\w-]+)#([0-9]+)`)
	})
	return issueRegexp
}

var (
	once        sync.Once
	issueRegexp *regexp.Regexp
)

func toProtocolLink(snapshot source.Snapshot, m *protocol.ColumnMapper, target string, start, end token.Pos, fileKind source.FileKind) (protocol.DocumentLink, error) {
	var rng protocol.Range
	switch fileKind {
	case source.Go:
		spn, err := span.NewRange(snapshot.FileSet(), start, end).Span()
		if err != nil {
			return protocol.DocumentLink{}, err
		}
		rng, err = m.Range(spn)
		if err != nil {
			return protocol.DocumentLink{}, err
		}
	case source.Mod:
		s, e := int(start), int(end)
		line, col, err := m.Converter.ToPosition(s)
		if err != nil {
			return protocol.DocumentLink{}, err
		}
		start := span.NewPoint(line, col, s)
		line, col, err = m.Converter.ToPosition(e)
		if err != nil {
			return protocol.DocumentLink{}, err
		}
		end := span.NewPoint(line, col, e)
		rng, err = m.Range(span.New(m.URI, start, end))
		if err != nil {
			return protocol.DocumentLink{}, err
		}
	}
	return protocol.DocumentLink{
		Range:  rng,
		Target: target,
	}, nil
}
<commit_msg>internal/lsp: remove unused parameters from moduleAtVersion<commit_after>// Copyright 2018 The Go 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 lsp

import (
	"bytes"
	"context"
	"fmt"
	"go/ast"
	"go/token"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"golang.org/x/mod/modfile"
	"golang.org/x/tools/internal/event"
	"golang.org/x/tools/internal/lsp/debug/tag"
	"golang.org/x/tools/internal/lsp/protocol"
	"golang.org/x/tools/internal/lsp/source"
	"golang.org/x/tools/internal/span"
)

func (s *Server) documentLink(ctx context.Context, params *protocol.DocumentLinkParams) (links []protocol.DocumentLink, err error) {
	snapshot, fh, ok, release, err := s.beginFileRequest(ctx, params.TextDocument.URI, source.UnknownKind)
	defer release()
	if !ok {
		return nil, err
	}
	switch snapshot.View().FileKind(fh) {
	case source.Mod:
		links, err = modLinks(ctx, snapshot, fh)
	case source.Go:
		links, err = goLinks(ctx, snapshot, fh)
	}
	// Don't return errors for document links.
	if err != nil {
		event.Error(ctx, "failed to compute document links", err, tag.URI.Of(fh.URI()))
		return nil, nil
	}
	return links, nil
}

func modLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) {
	pm, err := snapshot.ParseMod(ctx, fh)
	if err != nil {
		return nil, err
	}
	var links []protocol.DocumentLink
	for _, req := range pm.File.Require {
		if req.Syntax == nil {
			continue
		}
		// See golang/go#36998: don't link to modules matching GOPRIVATE.
		if snapshot.View().IsGoPrivatePath(req.Mod.Path) {
			continue
		}
		dep := []byte(req.Mod.Path)
		s, e := req.Syntax.Start.Byte, req.Syntax.End.Byte
		i := bytes.Index(pm.Mapper.Content[s:e], dep)
		if i == -1 {
			continue
		}
		// Shift the start position to the location of the
		// dependency within the require statement.
		start, end := token.Pos(s+i), token.Pos(s+i+len(dep))
		target := source.BuildLink(snapshot.View().Options().LinkTarget, "mod/"+req.Mod.String(), "")
		l, err := toProtocolLink(snapshot, pm.Mapper, target, start, end, source.Mod)
		if err != nil {
			return nil, err
		}
		links = append(links, l)
	}
	// TODO(ridersofrohan): handle links for replace and exclude directives.
	if syntax := pm.File.Syntax; syntax == nil {
		return links, nil
	}
	// Get all the links that are contained in the comments of the file.
	for _, expr := range pm.File.Syntax.Stmt {
		comments := expr.Comment()
		if comments == nil {
			continue
		}
		for _, section := range [][]modfile.Comment{comments.Before, comments.Suffix, comments.After} {
			for _, comment := range section {
				l, err := findLinksInString(ctx, snapshot, comment.Token, token.Pos(comment.Start.Byte), pm.Mapper, source.Mod)
				if err != nil {
					return nil, err
				}
				links = append(links, l...)
			}
		}
	}
	return links, nil
}

func goLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) {
	view := snapshot.View()
	// We don't actually need type information, so any typecheck mode is fine.
	pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckWorkspace, source.WidestPackage)
	if err != nil {
		return nil, err
	}
	pgf, err := snapshot.ParseGo(ctx, fh, source.ParseFull)
	if err != nil {
		return nil, err
	}
	var imports []*ast.ImportSpec
	var str []*ast.BasicLit
	ast.Inspect(pgf.File, func(node ast.Node) bool {
		switch n := node.(type) {
		case *ast.ImportSpec:
			imports = append(imports, n)
			return false
		case *ast.BasicLit:
			// Look for links in string literals.
			if n.Kind == token.STRING {
				str = append(str, n)
			}
			return false
		}
		return true
	})
	var links []protocol.DocumentLink
	// For import specs, provide a link to a documentation website, like
	// https://pkg.go.dev.
	if view.Options().ImportShortcut.ShowLinks() {
		for _, imp := range imports {
			target, err := strconv.Unquote(imp.Path.Value)
			if err != nil {
				continue
			}
			// See golang/go#36998: don't link to modules matching GOPRIVATE.
			if view.IsGoPrivatePath(target) {
				continue
			}
			if mod, version, ok := moduleAtVersion(target, pkg); ok && strings.ToLower(view.Options().LinkTarget) == "pkg.go.dev" {
				target = strings.Replace(target, mod, mod+"@"+version, 1)
			}
			// Account for the quotation marks in the positions.
			start := imp.Path.Pos() + 1
			end := imp.Path.End() - 1
			target = source.BuildLink(view.Options().LinkTarget, target, "")
			l, err := toProtocolLink(snapshot, pgf.Mapper, target, start, end, source.Go)
			if err != nil {
				return nil, err
			}
			links = append(links, l)
		}
	}
	for _, s := range str {
		l, err := findLinksInString(ctx, snapshot, s.Value, s.Pos(), pgf.Mapper, source.Go)
		if err != nil {
			return nil, err
		}
		links = append(links, l...)
	}
	for _, commentGroup := range pgf.File.Comments {
		for _, comment := range commentGroup.List {
			l, err := findLinksInString(ctx, snapshot, comment.Text, comment.Pos(), pgf.Mapper, source.Go)
			if err != nil {
				return nil, err
			}
			links = append(links, l...)
		}
	}
	return links, nil
}

func moduleAtVersion(target string, pkg source.Package) (string, string, bool) {
	impPkg, err := pkg.GetImport(target)
	if err != nil {
		return "", "", false
	}
	if impPkg.Version() == nil {
		return "", "", false
	}
	version, modpath := impPkg.Version().Version, impPkg.Version().Path
	if modpath == "" || version == "" {
		return "", "", false
	}
	return modpath, version, true
}

func findLinksInString(ctx context.Context, snapshot source.Snapshot, src string, pos token.Pos, m *protocol.ColumnMapper, fileKind source.FileKind) ([]protocol.DocumentLink, error) {
	var links []protocol.DocumentLink
	for _, index := range snapshot.View().Options().URLRegexp.FindAllIndex([]byte(src), -1) {
		start, end := index[0], index[1]
		startPos := token.Pos(int(pos) + start)
		endPos := token.Pos(int(pos) + end)
		link := src[start:end]
		linkURL, err := url.Parse(link)
		// Fallback: Linkify IP addresses as suggested in golang/go#18824.
		if err != nil {
			linkURL, err = url.Parse("//" + link)
			// Not all potential links will be valid, so don't return this error.
			if err != nil {
				continue
			}
		}
		// If the URL has no scheme, use https.
		if linkURL.Scheme == "" {
			linkURL.Scheme = "https"
		}
		l, err := toProtocolLink(snapshot, m, linkURL.String(), startPos, endPos, fileKind)
		if err != nil {
			return nil, err
		}
		links = append(links, l)
	}
	// Handle golang/go#1234-style links.
	r := getIssueRegexp()
	for _, index := range r.FindAllIndex([]byte(src), -1) {
		start, end := index[0], index[1]
		startPos := token.Pos(int(pos) + start)
		endPos := token.Pos(int(pos) + end)
		matches := r.FindStringSubmatch(src)
		if len(matches) < 4 {
			continue
		}
		org, repo, number := matches[1], matches[2], matches[3]
		target := fmt.Sprintf("https://github.com/%s/%s/issues/%s", org, repo, number)
		l, err := toProtocolLink(snapshot, m, target, startPos, endPos, fileKind)
		if err != nil {
			return nil, err
		}
		links = append(links, l)
	}
	return links, nil
}

func getIssueRegexp() *regexp.Regexp {
	once.Do(func() {
		issueRegexp = regexp.MustCompile(`(\w+)/([\w-]+)#([0-9]+)`)
	})
	return issueRegexp
}

var (
	once        sync.Once
	issueRegexp *regexp.Regexp
)

func toProtocolLink(snapshot source.Snapshot, m *protocol.ColumnMapper, target string, start, end token.Pos, fileKind source.FileKind) (protocol.DocumentLink, error) {
	var rng protocol.Range
	switch fileKind {
	case source.Go:
		spn, err := span.NewRange(snapshot.FileSet(), start, end).Span()
		if err != nil {
			return protocol.DocumentLink{}, err
		}
		rng, err = m.Range(spn)
		if err != nil {
			return protocol.DocumentLink{}, err
		}
	case source.Mod:
		s, e := int(start), int(end)
		line, col, err := m.Converter.ToPosition(s)
		if err != nil {
			return protocol.DocumentLink{}, err
		}
		start := span.NewPoint(line, col, s)
		line, col, err = m.Converter.ToPosition(e)
		if err != nil {
			return protocol.DocumentLink{}, err
		}
		end := span.NewPoint(line, col, e)
		rng, err = m.Range(span.New(m.URI, start, end))
		if err != nil {
			return protocol.DocumentLink{}, err
		}
	}
	return protocol.DocumentLink{
		Range:  rng,
		Target: target,
	}, nil
}
<|endoftext|>


<commit_before>package placer

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"syscall"

	"github.com/polydawn/gosh"
	"github.com/spacemonkeygo/errors"
	"github.com/spacemonkeygo/errors/try"
	"polydawn.net/repeatr/def"
	"polydawn.net/repeatr/io"
	"polydawn.net/repeatr/lib/fs"
	"polydawn.net/repeatr/lib/fspatch"
)

var _ integrity.Placer = CopyingPlacer

func CopyingPlacer(srcBasePath, destBasePath string, _ bool) integrity.Emplacement {
	srcBaseStat, err := os.Stat(srcBasePath)
	if err != nil || !srcBaseStat.IsDir() {
		panic(Error.New("copyingplacer: srcPath %q must be dir: %s", srcBasePath, err))
	}
	destBaseStat, err := os.Stat(destBasePath)
	if err != nil || !destBaseStat.IsDir() {
		panic(Error.New("copyingplacer: destPath %q must be dir: %s", destBasePath, err))
	}
	// remove any files already here (to emulate behavior like an overlapping mount)
	// (can't take the easy route and just `os.RemoveAll(destBasePath)` because that propagates times changes onto the parent.)
	d, err := os.Open(destBasePath)
	if err != nil {
		panic(Error.New("copyingplacer: io error: %s", err))
	}
	names, err := d.Readdirnames(-1)
	if err != nil {
		panic(Error.New("copyingplacer: io error: %s", err))
	}
	for _, name := range names {
		err := os.RemoveAll(filepath.Join(destBasePath, name))
		if err != nil {
			panic(Error.New("copyingplacer: io error: %s", err))
		}
	}
	// walk and copy
	preVisit := func(filenode *fs.FilewalkNode) error {
		if filenode.Err != nil {
			return filenode.Err
		}
		hdr, file := fs.ScanFile(srcBasePath, filenode.Path, filenode.Info)
		if file != nil {
			defer file.Close()
		}
		fs.PlaceFile(destBasePath, hdr, file)
		return nil
	}
	postVisit := func(filenode *fs.FilewalkNode) error {
		if filenode.Info.IsDir() {
			if err := fspatch.UtimesNano(filepath.Join(destBasePath, filenode.Path), def.Epochwhen, filenode.Info.ModTime()); err != nil {
				return err
			}
		}
		return nil
	}
	try.Do(func() {
		if err := fs.Walk(srcBasePath, preVisit, postVisit); err != nil {
			panic(err)
		}
	}).CatchAll(func(err error) {
		panic(Error.New("copyingplacer: io failed: ", err))
	}).Done()

	return copyEmplacement{path: destBasePath}
}

type copyEmplacement struct {
	path string
}

func (e copyEmplacement) Teardown() {
	if err := os.RemoveAll(e.path); err != nil {
		panic(Error.New("copyingplacer: teardown failed: ", err))
	}
}

var _ integrity.Placer = BindPlacer

func BindPlacer(srcPath, destPath string, writable bool) integrity.Emplacement {
	srcStat, err := os.Stat(srcPath)
	if err != nil || !srcStat.IsDir() {
		panic(Error.New("bindplacer: srcPath %q must be dir: %s", srcPath, err))
	}
	destStat, err := os.Stat(destPath)
	if err != nil || !destStat.IsDir() {
		panic(Error.New("bindplacer: destPath %q must be dir: %s", destPath, err))
	}
	flags := syscall.MS_BIND | syscall.MS_REC
	if !writable {
		flags |= syscall.MS_RDONLY
	}
	if err := syscall.Mount(srcPath, destPath, "bind", uintptr(flags), ""); err != nil {
		panic(Error.New("bindplacer: bind error: %s", err))
	}
	return bindEmplacement{path: destPath}
}

type bindEmplacement struct {
	path string
}

func (e bindEmplacement) Teardown() {
	if err := syscall.Unmount(e.path, 0); err != nil {
		panic(Error.New("bindplacer: teardown failed: ", err))
	}
}

func NewAufsPlacer(workPath string) integrity.Placer {
	workPath, err := filepath.Abs(workPath)
	if err != nil {
		panic(errors.IOError.Wrap(err))
	}
	return func(srcBasePath, destBasePath string, writable bool) integrity.Emplacement {
		srcBaseStat, err := os.Stat(srcBasePath)
		if err != nil || !srcBaseStat.IsDir() {
			panic(Error.New("aufsplacer: srcPath %q must be dir: %s", srcBasePath, err))
		}
		destBaseStat, err := os.Stat(destBasePath)
		if err != nil || !destBaseStat.IsDir() {
			panic(Error.New("aufsplacer: destPath %q must be dir: %s", destBasePath, err))
		}
		// if a RO mount is requested, no need to set up COW; just hand off to bind.
		if !writable {
			return BindPlacer(srcBasePath, destBasePath, writable)
		}
		// make work dir for the overlay layer
		layerPath, err := ioutil.TempDir(workPath, "layer-")
		if err != nil {
			panic(errors.IOError.Wrap(err))
		}
		// set up COW
		// if you were doing this in a shell, it'd be roughly `mount -t aufs -o br="$layer":"$base" none "$composite"`.
		// yes, this may behave oddly in the event of paths containing ":".
		gosh.Sh(
			"mount",
			"-t", "aufs",
			"-o", "br="+layerPath+":"+srcBasePath,
			"none",
			destBasePath,
		)
		// that's it; setting up COW also mounted it into destination.
		return aufsEmplacement{
			layerPath:   layerPath,
			landingPath: destBasePath,
		}
	}
}

type aufsEmplacement struct {
	layerPath   string
	landingPath string
}

func (e aufsEmplacement) Teardown() {
	// first tear down the mount
	if err := syscall.Unmount(e.landingPath, 0); err != nil {
		panic(Error.New("aufsplacer: teardown failed: ", err))
	}
	// now throw away the layer contents
	if err := os.RemoveAll(e.layerPath); err != nil {
		panic(Error.New("aufsplacer: teardown failed: ", err))
	}
}
<commit_msg>Fix error fmt strings.<commit_after>package placer

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"syscall"

	"github.com/polydawn/gosh"
	"github.com/spacemonkeygo/errors"
	"github.com/spacemonkeygo/errors/try"
	"polydawn.net/repeatr/def"
	"polydawn.net/repeatr/io"
	"polydawn.net/repeatr/lib/fs"
	"polydawn.net/repeatr/lib/fspatch"
)

var _ integrity.Placer = CopyingPlacer

func CopyingPlacer(srcBasePath, destBasePath string, _ bool) integrity.Emplacement {
	srcBaseStat, err := os.Stat(srcBasePath)
	if err != nil || !srcBaseStat.IsDir() {
		panic(Error.New("copyingplacer: srcPath %q must be dir: %s", srcBasePath, err))
	}
	destBaseStat, err := os.Stat(destBasePath)
	if err != nil || !destBaseStat.IsDir() {
		panic(Error.New("copyingplacer: destPath %q must be dir: %s", destBasePath, err))
	}
	// remove any files already here (to emulate behavior like an overlapping mount)
	// (can't take the easy route and just `os.RemoveAll(destBasePath)` because that propagates times changes onto the parent.)
	d, err := os.Open(destBasePath)
	if err != nil {
		panic(Error.New("copyingplacer: io error: %s", err))
	}
	names, err := d.Readdirnames(-1)
	if err != nil {
		panic(Error.New("copyingplacer: io error: %s", err))
	}
	for _, name := range names {
		err := os.RemoveAll(filepath.Join(destBasePath, name))
		if err != nil {
			panic(Error.New("copyingplacer: io error: %s", err))
		}
	}
	// walk and copy
	preVisit := func(filenode *fs.FilewalkNode) error {
		if filenode.Err != nil {
			return filenode.Err
		}
		hdr, file := fs.ScanFile(srcBasePath, filenode.Path, filenode.Info)
		if file != nil {
			defer file.Close()
		}
		fs.PlaceFile(destBasePath, hdr, file)
		return nil
	}
	postVisit := func(filenode *fs.FilewalkNode) error {
		if filenode.Info.IsDir() {
			if err := fspatch.UtimesNano(filepath.Join(destBasePath, filenode.Path), def.Epochwhen, filenode.Info.ModTime()); err != nil {
				return err
			}
		}
		return nil
	}
	try.Do(func() {
		if err := fs.Walk(srcBasePath, preVisit, postVisit); err != nil {
			panic(err)
		}
	}).CatchAll(func(err error) {
		panic(Error.New("copyingplacer: io failed: %s", err))
	}).Done()

	return copyEmplacement{path: destBasePath}
}

type copyEmplacement struct {
	path string
}

func (e copyEmplacement) Teardown() {
	if err := os.RemoveAll(e.path); err != nil {
		panic(Error.New("copyingplacer: teardown failed: %s", err))
	}
}

var _ integrity.Placer = BindPlacer

func BindPlacer(srcPath, destPath string, writable bool) integrity.Emplacement {
	srcStat, err := os.Stat(srcPath)
	if err != nil || !srcStat.IsDir() {
		panic(Error.New("bindplacer: srcPath %q must be dir: %s", srcPath, err))
	}
	destStat, err := os.Stat(destPath)
	if err != nil || !destStat.IsDir() {
		panic(Error.New("bindplacer: destPath %q must be dir: %s", destPath, err))
	}
	flags := syscall.MS_BIND | syscall.MS_REC
	if !writable {
		flags |= syscall.MS_RDONLY
	}
	if err := syscall.Mount(srcPath, destPath, "bind", uintptr(flags), ""); err != nil {
		panic(Error.New("bindplacer: bind error: %s", err))
	}
	return bindEmplacement{path: destPath}
}

type bindEmplacement struct {
	path string
}

func (e bindEmplacement) Teardown() {
	if err := syscall.Unmount(e.path, 0); err != nil {
		panic(Error.New("bindplacer: teardown failed: %s", err))
	}
}

func NewAufsPlacer(workPath string) integrity.Placer {
	workPath, err := filepath.Abs(workPath)
	if err != nil {
		panic(errors.IOError.Wrap(err))
	}
	return func(srcBasePath, destBasePath string, writable bool) integrity.Emplacement {
		srcBaseStat, err := os.Stat(srcBasePath)
		if err != nil || !srcBaseStat.IsDir() {
			panic(Error.New("aufsplacer: srcPath %q must be dir: %s", srcBasePath, err))
		}
		destBaseStat, err := os.Stat(destBasePath)
		if err != nil || !destBaseStat.IsDir() {
			panic(Error.New("aufsplacer: destPath %q must be dir: %s", destBasePath, err))
		}
		// if a RO mount is requested, no need to set up COW; just hand off to bind.
		if !writable {
			return BindPlacer(srcBasePath, destBasePath, writable)
		}
		// make work dir for the overlay layer
		layerPath, err := ioutil.TempDir(workPath, "layer-")
		if err != nil {
			panic(errors.IOError.Wrap(err))
		}
		// set up COW
		// if you were doing this in a shell, it'd be roughly `mount -t aufs -o br="$layer":"$base" none "$composite"`.
		// yes, this may behave oddly in the event of paths containing ":".
		gosh.Sh(
			"mount",
			"-t", "aufs",
			"-o", "br="+layerPath+":"+srcBasePath,
			"none",
			destBasePath,
		)
		// that's it; setting up COW also mounted it into destination.
		return aufsEmplacement{
			layerPath:   layerPath,
			landingPath: destBasePath,
		}
	}
}

type aufsEmplacement struct {
	layerPath   string
	landingPath string
}

func (e aufsEmplacement) Teardown() {
	// first tear down the mount
	if err := syscall.Unmount(e.landingPath, 0); err != nil {
		panic(Error.New("aufsplacer: teardown failed: %s", err))
	}
	// now throw away the layer contents
	if err := os.RemoveAll(e.layerPath); err != nil {
		panic(Error.New("aufsplacer: teardown failed: %s", err))
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
)

func TestAccDataSourceAWSLB_basic(t *testing.T) {
	lbName := fmt.Sprintf("testaccawslb-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccDataSourceAWSLBConfigBasic(lbName),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "name", lbName),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "internal", "true"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "subnets.#", "2"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "security_groups.#", "1"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "tags.%", "1"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "tags.TestName", "TestAccAWSALB_basic"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "enable_deletion_protection", "false"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "idle_timeout", "30"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_arn", "vpc_id"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_arn", "zone_id"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_arn", "dns_name"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_arn", "arn"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "name", lbName),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "internal", "true"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "subnets.#", "2"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "security_groups.#", "1"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "tags.%", "1"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "tags.TestName", "TestAccAWSALB_basic"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "enable_deletion_protection", "false"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "idle_timeout", "30"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_name", "vpc_id"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_name", "zone_id"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_name", "dns_name"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_name", "arn"),
				),
			},
		},
	})
}

func TestAccDataSourceAWSLBBackwardsCompatibility(t *testing.T) {
	lbName := fmt.Sprintf("testaccawsalb-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccDataSourceAWSLBConfigBackardsCompatibility(lbName),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "name", lbName),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "internal", "true"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "subnets.#", "2"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "security_groups.#", "1"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "tags.%", "1"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "tags.TestName", "TestAccAWSALB_basic"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "enable_deletion_protection", "false"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "idle_timeout", "30"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_arn", "vpc_id"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_arn", "zone_id"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_arn", "dns_name"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_arn", "arn"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "name", lbName),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "internal", "true"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "subnets.#", "2"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "security_groups.#", "1"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "tags.%", "1"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "tags.TestName", "TestAccAWSALB_basic"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "enable_deletion_protection", "false"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "idle_timeout", "30"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_name", "vpc_id"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_name", "zone_id"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_name", "dns_name"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_name", "arn"),
				),
			},
		},
	})
}

func testAccDataSourceAWSLBConfigBasic(lbName string) string {
	return fmt.Sprintf(`resource "aws_lb" "alb_test" {
  name            = "%s"
  internal        = true
  security_groups = ["${aws_security_group.alb_test.id}"]
  subnets         = ["${aws_subnet.alb_test.*.id}"]

  idle_timeout = 30
  enable_deletion_protection = false

  tags = {
    TestName = "TestAccAWSALB_basic"
  }
}

variable "subnets" {
  default = ["10.0.1.0/24", "10.0.2.0/24"]
  type    = "list"
}

data "aws_availability_zones" "available" {}

resource "aws_vpc" "alb_test" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "terraform-testacc-lb-data-source-basic"
  }
}

resource "aws_subnet" "alb_test" {
  count                   = 2
  vpc_id                  = "${aws_vpc.alb_test.id}"
  cidr_block              = "${element(var.subnets, count.index)}"
  map_public_ip_on_launch = true
  availability_zone       = "${element(data.aws_availability_zones.available.names, count.index)}"

  tags = {
    Name = "tf-acc-lb-data-source-basic"
  }
}

resource "aws_security_group" "alb_test" {
  name        = "allow_all_alb_test"
  description = "Used for ALB Testing"
  vpc_id      = "${aws_vpc.alb_test.id}"

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    TestName = "TestAccAWSALB_basic"
  }
}

data "aws_lb" "alb_test_with_arn" {
	arn = "${aws_lb.alb_test.arn}"
}

data "aws_lb" "alb_test_with_name" {
	name = "${aws_lb.alb_test.name}"
}`, lbName)
}

func testAccDataSourceAWSLBConfigBackardsCompatibility(albName string) string {
	return fmt.Sprintf(`resource "aws_alb" "alb_test" {
  name            = "%s"
  internal        = true
  security_groups = ["${aws_security_group.alb_test.id}"]
  subnets         = ["${aws_subnet.alb_test.*.id}"]

  idle_timeout = 30
  enable_deletion_protection = false

  tags = {
    TestName = "TestAccAWSALB_basic"
  }
}

variable "subnets" {
  default = ["10.0.1.0/24", "10.0.2.0/24"]
  type    = "list"
}

data "aws_availability_zones" "available" {}

resource "aws_vpc" "alb_test" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "terraform-testacc-lb-data-source-bc"
  }
}

resource "aws_subnet" "alb_test" {
  count                   = 2
  vpc_id                  = "${aws_vpc.alb_test.id}"
  cidr_block              = "${element(var.subnets, count.index)}"
  map_public_ip_on_launch = true
  availability_zone       = "${element(data.aws_availability_zones.available.names, count.index)}"

  tags = {
    Name = "tf-acc-lb-data-source-bc"
  }
}

resource "aws_security_group" "alb_test" {
  name        = "allow_all_alb_test"
  description = "Used for ALB Testing"
  vpc_id      = "${aws_vpc.alb_test.id}"

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    TestName = "TestAccAWSALB_basic"
  }
}

data "aws_alb" "alb_test_with_arn" {
	arn = "${aws_alb.alb_test.arn}"
}

data "aws_alb" "alb_test_with_name" {
	name = "${aws_alb.alb_test.name}"
}`, albName)
}
<commit_msg>Fixing tests for TestAccDataSourceAWSLB 0.12<commit_after>package aws

import (
	"fmt"
	"testing"

	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
)

func TestAccDataSourceAWSLB_basic(t *testing.T) {
	lbName := fmt.Sprintf("testaccawslb-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccDataSourceAWSLBConfigBasic(lbName),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "name", lbName),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "internal", "true"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "subnets.#", "2"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "security_groups.#", "1"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "tags.%", "1"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "tags.TestName", "TestAccAWSALB_basic"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "enable_deletion_protection", "false"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_arn", "idle_timeout", "30"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_arn", "vpc_id"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_arn", "zone_id"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_arn", "dns_name"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_arn", "arn"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "name", lbName),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "internal", "true"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "subnets.#", "2"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "security_groups.#", "1"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "tags.%", "1"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "tags.TestName", "TestAccAWSALB_basic"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "enable_deletion_protection", "false"),
					resource.TestCheckResourceAttr("data.aws_lb.alb_test_with_name", "idle_timeout", "30"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_name", "vpc_id"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_name", "zone_id"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_name", "dns_name"),
					resource.TestCheckResourceAttrSet("data.aws_lb.alb_test_with_name", "arn"),
				),
			},
		},
	})
}

func TestAccDataSourceAWSLBBackwardsCompatibility(t *testing.T) {
	lbName := fmt.Sprintf("testaccawsalb-basic-%s", acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum))

	resource.ParallelTest(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			{
				Config: testAccDataSourceAWSLBConfigBackardsCompatibility(lbName),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "name", lbName),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "internal", "true"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "subnets.#", "2"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "security_groups.#", "1"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "tags.%", "1"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "tags.TestName", "TestAccAWSALB_basic"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "enable_deletion_protection", "false"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_arn", "idle_timeout", "30"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_arn", "vpc_id"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_arn", "zone_id"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_arn", "dns_name"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_arn", "arn"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "name", lbName),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "internal", "true"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "subnets.#", "2"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "security_groups.#", "1"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "tags.%", "1"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "tags.TestName", "TestAccAWSALB_basic"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "enable_deletion_protection", "false"),
					resource.TestCheckResourceAttr("data.aws_alb.alb_test_with_name", "idle_timeout", "30"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_name", "vpc_id"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_name", "zone_id"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_name", "dns_name"),
					resource.TestCheckResourceAttrSet("data.aws_alb.alb_test_with_name", "arn"),
				),
			},
		},
	})
}

func testAccDataSourceAWSLBConfigBasic(lbName string) string {
	return fmt.Sprintf(`resource "aws_lb" "alb_test" {
  name            = "%s"
  internal        = true
  security_groups = ["${aws_security_group.alb_test.id}"]
  subnets         = ["${aws_subnet.alb_test.0.id}", "${aws_subnet.alb_test.1.id}"]

  idle_timeout = 30
  enable_deletion_protection = false

  tags = {
    TestName = "TestAccAWSALB_basic"
  }
}

variable "subnets" {
  default = ["10.0.1.0/24", "10.0.2.0/24"]
  type    = "list"
}

data "aws_availability_zones" "available" {}

resource "aws_vpc" "alb_test" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "terraform-testacc-lb-data-source-basic"
  }
}

resource "aws_subnet" "alb_test" {
  count                   = 2
  vpc_id                  = "${aws_vpc.alb_test.id}"
  cidr_block              = "${element(var.subnets, count.index)}"
  map_public_ip_on_launch = true
  availability_zone       = "${element(data.aws_availability_zones.available.names, count.index)}"

  tags = {
    Name = "tf-acc-lb-data-source-basic"
  }
}

resource "aws_security_group" "alb_test" {
  name        = "allow_all_alb_test"
  description = "Used for ALB Testing"
  vpc_id      = "${aws_vpc.alb_test.id}"

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    TestName = "TestAccAWSALB_basic"
  }
}

data "aws_lb" "alb_test_with_arn" {
	arn = "${aws_lb.alb_test.arn}"
}

data "aws_lb" "alb_test_with_name" {
	name = "${aws_lb.alb_test.name}"
}`, lbName)
}

func testAccDataSourceAWSLBConfigBackardsCompatibility(albName string) string {
	return fmt.Sprintf(`resource "aws_alb" "alb_test" {
  name            = "%s"
  internal        = true
  security_groups = ["${aws_security_group.alb_test.id}"]
  subnets         = ["${aws_subnet.alb_test.0.id}", "${aws_subnet.alb_test.1.id}"]

  idle_timeout = 30
  enable_deletion_protection = false

  tags = {
    TestName = "TestAccAWSALB_basic"
  }
}

variable "subnets" {
  default = ["10.0.1.0/24", "10.0.2.0/24"]
  type    = "list"
}

data "aws_availability_zones" "available" {}

resource "aws_vpc" "alb_test" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = "terraform-testacc-lb-data-source-bc"
  }
}

resource "aws_subnet" "alb_test" {
  count                   = 2
  vpc_id                  = "${aws_vpc.alb_test.id}"
  cidr_block              = "${element(var.subnets, count.index)}"
  map_public_ip_on_launch = true
  availability_zone       = "${element(data.aws_availability_zones.available.names, count.index)}"

  tags = {
    Name = "tf-acc-lb-data-source-bc"
  }
}

resource "aws_security_group" "alb_test" {
  name        = "allow_all_alb_test"
  description = "Used for ALB Testing"
  vpc_id      = "${aws_vpc.alb_test.id}"

  ingress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    TestName = "TestAccAWSALB_basic"
  }
}

data "aws_alb" "alb_test_with_arn" {
	arn = "${aws_alb.alb_test.arn}"
}

data "aws_alb" "alb_test_with_name" {
	name = "${aws_alb.alb_test.name}"
}`, albName)
}
<|endoftext|>


<commit_before>package medias

import (
	"encoding/json"
	"errors"
	"github.com/mitchellh/mapstructure"
	"io/ioutil"
	"log"
	"os"
	"fmt"
	"github.com/Zenika/MARCEL/backend/commons"
	"github.com/Zenika/MARCEL/backend/plugins"
	"github.com/Zenika/MARCEL/backend/containers"
	"strconv"
)

type Manager struct {
	configPath     string
	configFileName string
	configFullpath string
	Config         *Configuration

	pluginManager *plugins.Manager
}

func NewManager(pluginManager *plugins.Manager, configPath, configFilename string) *Manager {
	manager := new(Manager)

	manager.configPath = configPath
	manager.configFileName = configFilename

	manager.pluginManager = pluginManager

	manager.configFullpath = fmt.Sprintf("%s%c%s", configPath, os.PathSeparator, configFilename)
	manager.Config = NewConfiguration()

	return manager
}

// LoadMedias loads medias configuration from DB and stor it in memory
func (m *Manager) Load() {
	log.Printf("Start Loading Medias from DB.")

	m.CreateSaveFileIfNotExist(m.configPath, m.configFileName)

	//Medias configurations are loaded from a JSON file on the FS.
	content, err := ioutil.ReadFile(m.configFullpath)
	commons.Check(err)

	var obj interface{}
	json.Unmarshal([]byte(content), &obj)

	if obj == nil {
		obj = make(map[string]interface{})
	}
	err = mapstructure.Decode(obj.(map[string]interface{}), m.Config)
	if err != nil {
		panic(err)
	}

	log.Print("Medias configurations is loaded...")
}

func (m *Manager) GetConfiguration() (*Configuration) {
	log.Println("Getting global medias config")

	return m.Config
}

func (m *Manager) GetAll() ([]Media) {
	log.Println("Getting all medias")

	return m.Config.Medias
}

// GetMedia Return the media with this id
func (m *Manager) Get(idMedia int) (*Media, error) {

	log.Println("Getting media with id: ", idMedia)
	for _, media := range m.Config.Medias {
		if idMedia == media.ID {
			return &media, nil
		}
	}

	return nil, errors.New("NO_MEDIA_FOUND")
}

// CreateMedia Create a new Media, save it into memory and commit
func (m *Manager) CreateEmpty() (*Media) {

	log.Println("Creating media")

	newMedia := NewMedia()
	newMedia.ID = m.GetNextID()

	//save it into the MediasConfiguration
	m.Save(newMedia)
	m.Commit()

	return newMedia
}

// RemoveMedia Remove media from memory
func (m *Manager) Remove(media *Media) {
	log.Println("Removing media")
	i := m.getPosition(media)

	if i >= 0 {
		m.Config.Medias = append(m.Config.Medias[:i], m.Config.Medias[i+1:]...)
	}
}

// SaveMedia Save media information in memory.
func (m *Manager) Save(media *Media) {
	log.Println("Saving media")
	m.Remove(media)
	m.Config.Medias = append(m.Config.Medias, *media)
}

// Commit Save all medias in DB.
// Here DB is a JSON file
func (m *Manager) Commit() {
	content, _ := json.Marshal(m.Config)

	err := ioutil.WriteFile(m.configFullpath, content, 0644)

	if err != nil {
		log.Println("Cannot save medias configuration:")
		log.Panic(err)
	}
}

// CreateSaveFileIfNotExist check if the save file for medias exists and create it if not.
func (m *Manager) CreateSaveFileIfNotExist(filePath string, fileName string) {

	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		log.Println("Data directory did not exist. Create it.")
		os.Mkdir(filePath, 0755)
	}

	var fullPath string = fmt.Sprintf("%s%c%s", filePath, os.PathSeparator, fileName)
	if _, err := os.Stat(fullPath); os.IsNotExist(err) {

		f, err := os.Create(fullPath)
		commons.Check(err)

		log.Println("Medias configuration file created at %v", fullPath)

		f.Close()

		//commit a first time to ensure the configuration has been saved
		m.Commit()
	}
}

func (m *Manager) Activate(media *Media) error {

	sep := string(os.PathSeparator)

	fmt.Printf("Media '%v' has plugin : ", media.Name)
	for _, mp := range media.Plugins {

		plugin, err := m.pluginManager.Get(mp.EltName)
		if err != nil {
			//plugin does not exist (anymore ?) in the catalog. Obviously, it should never append.
			log.Println(err.Error())
			//Don't return an error now, we need to activate the other plugins
		}

		// 1 : duplicate plugin files into "medias/{idMedia}/{plugins_EltName}/{idInstance}"
		mpPath := "medias" + sep + strconv.Itoa(media.ID) + sep + mp.EltName + sep + mp.InstanceId
		err = commons.CopyDir("plugins"+sep+mp.EltName, mpPath)

		if err != nil {
			return err
		}

		if mp.BackEnd != nil {
			containers.InstallImage(mpPath + sep + "back" + sep + plugin.Backend.Dockerimage)

			//containers.StartContainer(plugin.Backend.)

		}

		// 2.b : if p has backend
		//           => pull backend image or load it (if it has been tar.gzed) into "/medias/{idMedia}/{plugins_EltName}/{idInstance}/back"
		//           => create a dedicated volume
		//           => get a new port to be mapped with the backend, from the pool of ports (getPortNumberForPlugin)
		//           => run docker container
		//           => save the name of the container into a map to be easily stopped later (with Deactivate)

		//create a new instance for the plugin, i.e.:
		//   - duplicate the folder into /medias/{idMedia}/{plugins_EltName}/{idInstance}
		//   - if the plugin has a backend to launch: map the port and run docker container...

	}

	media.IsActive = true

	m.Save(media)

	return nil
}

func (m *Manager) Deactivate(media *Media) error {

	//stop all backends instances and free ports number
	for _, mp := range media.Plugins {
		if mp.BackEnd != nil {

			//todo : call docker manager to stop container for this plugin
			//containerManager.stopImage(mp.BackEnd.ContainerID)

			if mp.BackEnd.Ports != nil {
				ports := mp.BackEnd.Ports
				for _, port := range ports {
					m.FreePortNumberForPlugin(port)
				}
			}
		}
	}

	//remove plugins files
	sep := string(os.PathSeparator)
	os.RemoveAll("medias" + sep + strconv.Itoa(media.ID))

	media.IsActive = false

	m.Save(media)

	return nil
}

// GetMediaPosition Return position of a media in the list
func (m *Manager) getPosition(media *Media) int {
	for p, m := range m.Config.Medias {
		if m.ID == media.ID {
			return p
		}
	}
	return -1
}

func (m *Manager) GetPortNumberForPlugin() int {

	// 1 : try to pop a port number from the pool
	if len(m.Config.PortsPool) > 0 {
		p := m.Config.PortsPool[0]
		//remove the first number
		m.Config.PortsPool = m.Config.PortsPool[1:]
		return p
	}

	// 2 : if pool is empty, just increment the counter
	p := m.Config.NextFreePortNumber
	m.Config.NextFreePortNumber += 1

	return p
}

func (m *Manager) FreePortNumberForPlugin(portNumber int) {
	for i, v := range m.Config.PortsPool {
		if portNumber == v {
			m.Config.PortsPool = append(m.Config.PortsPool[:i], m.Config.PortsPool[i+1:]...)
		}
	}
}

func (m *Manager) GetNextID() int {
	m.Config.LastID = m.Config.LastID + 1
	return m.Config.LastID
}
<commit_msg>[Add] Start and Stop container<commit_after>package medias

import (
	"encoding/json"
	"errors"
	"github.com/mitchellh/mapstructure"
	"io/ioutil"
	"log"
	"os"
	"fmt"
	"github.com/Zenika/MARCEL/backend/commons"
	"github.com/Zenika/MARCEL/backend/plugins"
	"github.com/Zenika/MARCEL/backend/containers"
	"strconv"
	"strings"
)

type Manager struct {
	configPath     string
	configFileName string
	configFullpath string
	Config         *Configuration

	pluginManager *plugins.Manager
}

func NewManager(pluginManager *plugins.Manager, configPath, configFilename string) *Manager {
	manager := new(Manager)

	manager.configPath = configPath
	manager.configFileName = configFilename

	manager.pluginManager = pluginManager

	manager.configFullpath = fmt.Sprintf("%s%c%s", configPath, os.PathSeparator, configFilename)
	manager.Config = NewConfiguration()

	return manager
}

// LoadMedias loads medias configuration from DB and stor it in memory
func (m *Manager) Load() {
	log.Printf("Start Loading Medias from DB.")

	m.CreateSaveFileIfNotExist(m.configPath, m.configFileName)

	//Medias configurations are loaded from a JSON file on the FS.
	content, err := ioutil.ReadFile(m.configFullpath)
	commons.Check(err)

	var obj interface{}
	json.Unmarshal([]byte(content), &obj)

	if obj == nil {
		obj = make(map[string]interface{})
	}
	err = mapstructure.Decode(obj.(map[string]interface{}), m.Config)
	if err != nil {
		panic(err)
	}

	log.Print("Medias configurations is loaded...")
}

func (m *Manager) GetConfiguration() (*Configuration) {
	log.Println("Getting global medias config")

	return m.Config
}

func (m *Manager) GetAll() ([]Media) {
	log.Println("Getting all medias")

	return m.Config.Medias
}

// GetMedia Return the media with this id
func (m *Manager) Get(idMedia int) (*Media, error) {

	log.Println("Getting media with id: ", idMedia)
	for _, media := range m.Config.Medias {
		if idMedia == media.ID {
			return &media, nil
		}
	}

	return nil, errors.New("NO_MEDIA_FOUND")
}

// CreateMedia Create a new Media, save it into memory and commit
func (m *Manager) CreateEmpty() (*Media) {

	log.Println("Creating media")

	newMedia := NewMedia()
	newMedia.ID = m.GetNextID()

	//save it into the MediasConfiguration
	m.Save(newMedia)
	m.Commit()

	return newMedia
}

// RemoveMedia Remove media from memory
func (m *Manager) Remove(media *Media) {
	log.Println("Removing media")
	i := m.getPosition(media)

	if i >= 0 {
		m.Config.Medias = append(m.Config.Medias[:i], m.Config.Medias[i+1:]...)
	}
}

// SaveMedia Save media information in memory.
func (m *Manager) Save(media *Media) {
	log.Println("Saving media")
	m.Remove(media)
	m.Config.Medias = append(m.Config.Medias, *media)
}

// Commit Save all medias in DB.
// Here DB is a JSON file
func (m *Manager) Commit() {
	content, _ := json.Marshal(m.Config)

	err := ioutil.WriteFile(m.configFullpath, content, 0644)

	if err != nil {
		log.Println("Cannot save medias configuration:")
		log.Panic(err)
	}
}

// CreateSaveFileIfNotExist check if the save file for medias exists and create it if not.
func (m *Manager) CreateSaveFileIfNotExist(filePath string, fileName string) {

	if _, err := os.Stat(filePath); os.IsNotExist(err) {
		log.Println("Data directory did not exist. Create it.")
		os.Mkdir(filePath, 0755)
	}

	var fullPath string = fmt.Sprintf("%s%c%s", filePath, os.PathSeparator, fileName)
	if _, err := os.Stat(fullPath); os.IsNotExist(err) {

		f, err := os.Create(fullPath)
		commons.Check(err)

		log.Println("Medias configuration file created at %v", fullPath)

		f.Close()

		//commit a first time to ensure the configuration has been saved
		m.Commit()
	}
}

func (m *Manager) Activate(media *Media) error {

	sep := string(os.PathSeparator)

	for _, mp := range media.Plugins {

		plugin, err := m.pluginManager.Get(mp.EltName)
		if err != nil {
			//plugin does not exist (anymore ?) in the catalog. Obviously, it should never append.
			log.Println(err.Error())
			//Don't return an error now, we need to activate the other plugins
		}

		// 1 : duplicate plugin files into "medias/{idMedia}/{plugins_EltName}/{idInstance}"
		mpPath := "medias" + sep + strconv.Itoa(media.ID) + sep + mp.EltName + sep + mp.InstanceId
		err = commons.CopyDir("plugins"+sep+mp.EltName, mpPath)

		if err != nil {
			return err
		}

		if mp.BackEnd != nil {
			retour, err := containers.InstallImage(mpPath + sep + "back" + sep + plugin.Backend.Dockerimage)

			if err != nil {
				//Don't return an error now, we need to activate the other plugins
				log.Println(err.Error())
			}
			imageName := strings.TrimSpace(strings.TrimPrefix(retour, "Loaded image: "))
			externalPort := m.GetPortNumberForPlugin()

			dockerContainerId, err := containers.StartContainer(imageName, plugin.Backend.Port, externalPort, mp.BackEnd.Props)
			if err != nil {
				//Don't return an error now, we need to activate the other plugins
				log.Println(err.Error())
			} else {
				mp.BackEnd.Port = externalPort
				mp.BackEnd.DockerImageName = imageName
				mp.BackEnd.DockerContainerId = dockerContainerId
			}
		}
	}

	media.IsActive = true

	m.Save(media)

	return nil
}

func (m *Manager) Deactivate(media *Media) error {

	//stop all backends instances and free ports number
	for _, mp := range media.Plugins {
		if mp.BackEnd != nil {

			containers.StopContainer(mp.BackEnd.DockerContainerId)

			m.FreePortNumberForPlugin(mp.BackEnd.Port)
		}
	}

	//remove plugins files
	sep := string(os.PathSeparator)
	os.RemoveAll("medias" + sep + strconv.Itoa(media.ID))

	media.IsActive = false

	m.Save(media)

	return nil
}

// GetMediaPosition Return position of a media in the list
func (m *Manager) getPosition(media *Media) int {
	for p, m := range m.Config.Medias {
		if m.ID == media.ID {
			return p
		}
	}
	return -1
}

func (m *Manager) GetPortNumberForPlugin() int {

	// 1 : try to pop a port number from the pool
	if len(m.Config.PortsPool) > 0 {
		p := m.Config.PortsPool[0]
		//remove the first number
		m.Config.PortsPool = m.Config.PortsPool[1:]
		return p
	}

	// 2 : if pool is empty, just increment the counter
	p := m.Config.NextFreePortNumber
	m.Config.NextFreePortNumber += 1

	return p
}

func (m *Manager) FreePortNumberForPlugin(portNumber int) {
	for i, v := range m.Config.PortsPool {
		if portNumber == v {
			m.Config.PortsPool = append(m.Config.PortsPool[:i], m.Config.PortsPool[i+1:]...)
		}
	}
}

func (m *Manager) GetNextID() int {
	m.Config.LastID = m.Config.LastID + 1
	return m.Config.LastID
}
<|endoftext|>


<commit_before>package boltdb

import (
	"os"
	"reflect"
	"testing"

	log "github.com/Sirupsen/logrus"
	"github.com/boltdb/bolt"
)

// TestDB - holds connection to database during tests
var TestDB *bolt.DB

var testingDatabaseName = "bolt_test.db"

func TestSetGetValue(t *testing.T) {
	db := NewBoltDBCache(TestDB, []byte("bucket1"))

	err := db.Set([]byte("foo"), []byte("bar"))
	expect(t, err, nil)

	val, err := db.Get([]byte("foo"))
	expect(t, string(val), "bar")
}

func setup() {
	// we don't really want to see what's happening
	log.SetLevel(log.FatalLevel)
	db := GetDB(testingDatabaseName)
	TestDB = db
}

// teardown does some cleanup after tests
func teardown() {
	TestDB.Close()
	os.Remove(testingDatabaseName)
}

// TestMain prepares database for testing and then performs a cleanup
func TestMain(m *testing.M) {

	setup()

	retCode := m.Run()

	// delete test database
	teardown()

	// call with result of m.Run()
	os.Exit(retCode)
}

func expect(t *testing.T, a interface{}, b interface{}) {
	if a != b {
		t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

func refute(t *testing.T, a interface{}, b interface{}) {
	if a == b {
		t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}
<commit_msg>some more tests<commit_after>package boltdb

import (
	"os"
	"reflect"
	"testing"

	log "github.com/Sirupsen/logrus"
	"github.com/boltdb/bolt"
)

// TestDB - holds connection to database during tests
var TestDB *bolt.DB

var testingDatabaseName = "bolt_test.db"

func TestSetGetValue(t *testing.T) {
	db := NewBoltDBCache(TestDB, []byte("bucket1"))

	err := db.Set([]byte("foo"), []byte("bar"))
	expect(t, err, nil)

	val, err := db.Get([]byte("foo"))
	expect(t, string(val), "bar")
}

func TestRecordsCountZero(t *testing.T) {
	db := NewBoltDBCache(TestDB, []byte("bucketRecordsCountZero"))

	ct, err := db.RecordsCount()
	expect(t, err, nil)
	expect(t, ct, 0)

}

func TestDeleteRecords(t *testing.T) {
	db := NewBoltDBCache(TestDB, []byte("bucketTestDeleteRecords"))

	err := db.Set([]byte("foo"), []byte("bar"))
	expect(t, err, nil)

	err = db.Set([]byte("foo2"), []byte("bar"))
	expect(t, err, nil)

	err = db.Set([]byte("foo3"), []byte("bar"))
	expect(t, err, nil)

	ct, err := db.RecordsCount()
	expect(t, err, nil)
	expect(t, ct, 3)

	err = db.DeleteData()
	expect(t, err, nil)

	ctNew, err := db.RecordsCount()
	expect(t, err, nil)
	expect(t, ctNew, 0)

}

func setup() {
	// we don't really want to see what's happening
	log.SetLevel(log.FatalLevel)
	db := GetDB(testingDatabaseName)
	TestDB = db
}

// teardown does some cleanup after tests
func teardown() {
	TestDB.Close()
	os.Remove(testingDatabaseName)
}

// TestMain prepares database for testing and then performs a cleanup
func TestMain(m *testing.M) {

	setup()

	retCode := m.Run()

	// delete test database
	teardown()

	// call with result of m.Run()
	os.Exit(retCode)
}

func expect(t *testing.T, a interface{}, b interface{}) {
	if a != b {
		t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

func refute(t *testing.T, a interface{}, b interface{}) {
	if a == b {
		t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}
<|endoftext|>


<commit_before>//
// Copyright (c) 2015 The heketi Authors
//
// 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 commands

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/heketi/heketi/apps/glusterfs"
	client "github.com/heketi/heketi/client/api/go-client"
	"github.com/lpabon/godbc"
)

type NodeAddCommand struct {
	Cmd
	zone               int
	managmentHostNames string
	storageHostNames   string
	clusterId          string
}

func NewNodeAddCommand(options *Options) *NodeAddCommand {

	godbc.Require(options != nil)

	cmd := &NodeAddCommand{}
	cmd.name = "add"
	cmd.options = options
	cmd.flags = flag.NewFlagSet(cmd.name, flag.ExitOnError)
	cmd.flags.IntVar(&cmd.zone, "zone", -1, "The zone in which the node should reside")
	cmd.flags.StringVar(&cmd.clusterId, "cluster", "", "The cluster in which the node should reside")
	cmd.flags.StringVar(&cmd.managmentHostNames, "management-host-name", "", "Managment host name")
	cmd.flags.StringVar(&cmd.storageHostNames, "storage-host-name", "", "Storage host name")

	//usage on -help
	cmd.flags.Usage = func() {
		fmt.Println(`
Add new node to be managed by Heketi

USAGE
  heketi-cli node add [options]

OPTIONS`)

		//print flags
		cmd.flags.PrintDefaults()
		fmt.Println(`
EXAMPLES
  $ heketi-cli node add \
      -zone=3 \
      -cluster=3e098cb4407d7109806bb196d9e8f095 \
      -managment-host-name=node1-manage.gluster.lab.com \
      -storage-host-name=node1-storage.gluster.lab.com
`)
	}
	godbc.Ensure(cmd.name == "add")

	return cmd
}

func (n *NodeAddCommand) Exec(args []string) error {

	// Parse args
	n.flags.Parse(args)

	// Check arguments
	if n.zone == -1 {
		return errors.New("Missing zone")
	}
	if n.managmentHostNames == "" {
		return errors.New("Missing management hostname")
	}
	if n.storageHostNames == "" {
		return errors.New("Missing management hostname")
	}
	if n.clusterId == "" {
		return errors.New("Missing cluster id")
	}

	// Create request blob
	req := &glusterfs.NodeAddRequest{}
	req.ClusterId = n.clusterId
	req.Hostnames.Manage = []string{n.managmentHostNames}
	req.Hostnames.Storage = []string{n.storageHostNames}
	req.Zone = n.zone

	// Create a client
	heketi := client.NewClient(n.options.Url, n.options.User, n.options.Key)

	// Add node
	node, err := heketi.NodeAdd(req)
	if err != nil {
		return err
	}

	if n.options.Json {
		data, err := json.Marshal(node)
		if err != nil {
			return err
		}
		fmt.Fprintf(stdout, string(data))
	} else {
		fmt.Fprintf(stdout, "Node information:\n"+
			"Id: %v\n"+
			"Cluster Id: %v\n"+
			"Zone: %v\n"+
			"Management Hostname %v\n"+
			"Storage Hostname %v\n",
			node.Id,
			node.ClusterId,
			node.Zone,
			node.Hostnames.Manage[0],
			node.Hostnames.Storage[0])
	}
	return nil
}
<commit_msg>Print correct error message<commit_after>//
// Copyright (c) 2015 The heketi Authors
//
// 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 commands

import (
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"github.com/heketi/heketi/apps/glusterfs"
	client "github.com/heketi/heketi/client/api/go-client"
	"github.com/lpabon/godbc"
)

type NodeAddCommand struct {
	Cmd
	zone               int
	managmentHostNames string
	storageHostNames   string
	clusterId          string
}

func NewNodeAddCommand(options *Options) *NodeAddCommand {

	godbc.Require(options != nil)

	cmd := &NodeAddCommand{}
	cmd.name = "add"
	cmd.options = options
	cmd.flags = flag.NewFlagSet(cmd.name, flag.ExitOnError)
	cmd.flags.IntVar(&cmd.zone, "zone", -1, "The zone in which the node should reside")
	cmd.flags.StringVar(&cmd.clusterId, "cluster", "", "The cluster in which the node should reside")
	cmd.flags.StringVar(&cmd.managmentHostNames, "management-host-name", "", "Managment host name")
	cmd.flags.StringVar(&cmd.storageHostNames, "storage-host-name", "", "Storage host name")

	//usage on -help
	cmd.flags.Usage = func() {
		fmt.Println(`
Add new node to be managed by Heketi

USAGE
  heketi-cli node add [options]

OPTIONS`)

		//print flags
		cmd.flags.PrintDefaults()
		fmt.Println(`
EXAMPLES
  $ heketi-cli node add \
      -zone=3 \
      -cluster=3e098cb4407d7109806bb196d9e8f095 \
      -managment-host-name=node1-manage.gluster.lab.com \
      -storage-host-name=node1-storage.gluster.lab.com
`)
	}
	godbc.Ensure(cmd.name == "add")

	return cmd
}

func (n *NodeAddCommand) Exec(args []string) error {

	// Parse args
	n.flags.Parse(args)

	// Check arguments
	if n.zone == -1 {
		return errors.New("Missing zone")
	}
	if n.managmentHostNames == "" {
		return errors.New("Missing management hostname")
	}
	if n.storageHostNames == "" {
		return errors.New("Missing storage hostname")
	}
	if n.clusterId == "" {
		return errors.New("Missing cluster id")
	}

	// Create request blob
	req := &glusterfs.NodeAddRequest{}
	req.ClusterId = n.clusterId
	req.Hostnames.Manage = []string{n.managmentHostNames}
	req.Hostnames.Storage = []string{n.storageHostNames}
	req.Zone = n.zone

	// Create a client
	heketi := client.NewClient(n.options.Url, n.options.User, n.options.Key)

	// Add node
	node, err := heketi.NodeAdd(req)
	if err != nil {
		return err
	}

	if n.options.Json {
		data, err := json.Marshal(node)
		if err != nil {
			return err
		}
		fmt.Fprintf(stdout, string(data))
	} else {
		fmt.Fprintf(stdout, "Node information:\n"+
			"Id: %v\n"+
			"Cluster Id: %v\n"+
			"Zone: %v\n"+
			"Management Hostname %v\n"+
			"Storage Hostname %v\n",
			node.Id,
			node.ClusterId,
			node.Zone,
			node.Hostnames.Manage[0],
			node.Hostnames.Storage[0])
	}
	return nil
}
<|endoftext|>