<commit_before>// Copyright 2016, RadiantBlue Technologies, 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 logger

import (
	"encoding/json"
	_ "fmt"
	"log"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/venicegeo/pz-gocommon/elasticsearch"
	"github.com/venicegeo/pz-gocommon/gocommon"
)

type LockedAdminStats struct {
	sync.Mutex
	LoggerAdminStats
}

type LogData struct {
	sync.Mutex
	esIndex elasticsearch.IIndex
	id      int
}

const schema = "LogData5"

type Service struct {
	stats   LockedAdminStats
	logData LogData
	origin  string
}

func (service *Service) Init(sys *piazza.SystemConfig, esIndex elasticsearch.IIndex) error {
	var err error

	service.stats.CreatedOn = time.Now()
	/***
	err = esIndex.Delete()
	if err != nil {
		log.Fatal(err)
	}
	if esIndex.IndexExists() {
		log.Fatal("index still exists")
	}
	err = esIndex.Create()
	if err != nil {
		log.Fatal(err)
	}
	***/

	if !esIndex.IndexExists() {
		log.Printf("Creating index: %s", esIndex.IndexName())
		err = esIndex.Create("")
		if err != nil {
			log.Fatal(err)
		}
	}

	if !esIndex.TypeExists(schema) {
		log.Printf("Creating type: %s", schema)

		mapping :=
			`{
			"LogData5":{
				"properties":{
					"service":{
						"type": "string",
						"store": true
					},
					"address":{
						"type": "string",
						"store": true
					},
					"createdOn":{
						"type": "date",
						"store": true
					},
					"severity":{
						"type": "string",
						"store": true
					},
					"message":{
						"type": "string",
						"store": true
					}
				}
			}
		}`

		err = esIndex.SetMapping(schema, piazza.JsonString(mapping))
		if err != nil {
			log.Printf("LoggerService.Init: %s", err.Error())
			return err
		}
	}

	service.logData.esIndex = esIndex

	service.origin = string(sys.Name)

	return nil
}

func (service *Service) newInternalErrorResponse(err error) *piazza.JsonResponse {
	return &piazza.JsonResponse{
		StatusCode: http.StatusInternalServerError,
		Message:    err.Error(),
		Origin:     service.origin,
	}
}

func (service *Service) newBadRequestResponse(err error) *piazza.JsonResponse {
	return &piazza.JsonResponse{
		StatusCode: http.StatusBadRequest,
		Message:    err.Error(),
		Origin:     service.origin,
	}
}

func (service *Service) GetRoot() *piazza.JsonResponse {
	resp := &piazza.JsonResponse{
		StatusCode: 200,
		Data:       "Hi. I'm pz-logger.",
	}

	err := resp.SetType()
	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	return resp
}

func (service *Service) PostMessage(mssg *Message) *piazza.JsonResponse {
	err := mssg.Validate()
	if err != nil {
		return service.newBadRequestResponse(err)
	}

	service.logData.Lock()
	idStr := strconv.Itoa(service.logData.id)
	service.logData.id++
	service.logData.Unlock()

	_, err = service.logData.esIndex.PostData(schema, idStr, mssg)
	if err != nil {
		//log.Printf("POST failed (1): %#v %#v", err, indexResult)
		return service.newInternalErrorResponse(err)
	}

	/*	if !indexResult.Created {
		log.Printf("POST failed (2): %#v", *indexResult)
		return NewInternalErrorResponse(err)
	}*/

	service.stats.LoggerAdminStats.NumMessages++

	resp := &piazza.JsonResponse{
		StatusCode: http.StatusOK,
		Data:       mssg,
	}

	err = resp.SetType()
	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	return resp
}

func (service *Service) GetStats() *piazza.JsonResponse {
	service.logData.Lock()
	t := service.stats.LoggerAdminStats
	service.logData.Unlock()

	resp := &piazza.JsonResponse{
		StatusCode: http.StatusOK,
		Data:       t,
	}

	err := resp.SetType()
	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	return resp
}

func (service *Service) GetMessage(params *piazza.HttpQueryParams) *piazza.JsonResponse {
	var err error

	defaultPagination := &piazza.JsonPagination{
		PerPage: 10,
		Page:    0,
		Order:   piazza.PaginationOrderDescending,
		SortBy:  "createdOn",
	}
	pagination, err := piazza.NewJsonPagination(params, defaultPagination)
	if err != nil {
		return service.newBadRequestResponse(err)
	}

	dsl, err := createQueryDslAsString(pagination, params)
	if err != nil {
		return service.newBadRequestResponse(err)
	}

	log.Printf("dsl: %s", dsl)

	var searchResult *elasticsearch.SearchResult

	if dsl == "" {
		searchResult, err = service.logData.esIndex.FilterByMatchAll(schema, pagination)
	} else {
		searchResult, err = service.logData.esIndex.SearchByJSON(schema, dsl)
	}

	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	// TODO: unsafe truncation
	matched := int(searchResult.NumberMatched())
	var lines []Message

	for _, hit := range *searchResult.GetHits() {
		if hit == nil {
			log.Printf("null source hit")
			continue
		}

		var msg *Message
		err = json.Unmarshal(*hit.Source, msg)
		if err != nil {
			log.Printf("UNABLE TO PARSE: %s", string(*hit.Source))
			return service.newInternalErrorResponse(err)
		}

		// just in case
		err = msg.Validate()
		if err != nil {
			log.Printf("UNABLE TO VALIDATE: %s", string(*hit.Source))
			continue
		}

		lines = append(lines, *msg)
	}

	pagination.Count = matched
	resp := &piazza.JsonResponse{
		StatusCode: http.StatusOK,
		Data:       lines,
		Pagination: pagination,
	}

	err = resp.SetType()
	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	return resp
}

func createQueryDslAsString(
	pagination *piazza.JsonPagination,
	params *piazza.HttpQueryParams) (string, error) {

	must := []map[string]interface{}{}

	service, err := params.AsString("service", nil)
	if err != nil {
		return "", err
	}

	contains, err := params.AsString("contains", nil)
	if err != nil {
		return "", err
	}

	before, err := params.AsTime("before", nil)
	if err != nil {
		return "", err
	}

	after, err := params.AsTime("after", nil)
	if err != nil {
		return "", err
	}

	if service != nil {
		must = append(must, map[string]interface{}{
			"match": map[string]interface{}{
				"service": *service,
			},
		})
	}

	if contains != nil {
		must = append(must, map[string]interface{}{
			"multi_match": map[string]interface{}{
				"query":  contains,
				"fields": []string{"address", "message", "service", "severity"},
			},
		})
	}

	if after != nil || before != nil {
		rangeParams := map[string]time.Time{}

		if after != nil {
			rangeParams["gte"] = *after
		}

		if before != nil {
			rangeParams["lte"] = *before
		}

		must = append(must, map[string]interface{}{
			"range": map[string]interface{}{
				"createdOn": rangeParams,
			},
		})
	}

	if len(must) == 0 {
		return "", nil
	}

	dsl := map[string]interface{}{
		"query": map[string]interface{}{
			"filtered": map[string]interface{}{
				"query": map[string]interface{}{
					"bool": map[string]interface{}{
						"must": must,
					},
				},
			},
		},
		"size": pagination.PerPage,
		"from": pagination.PerPage * pagination.Page,
	}

	dsl["sort"] = map[string]string{
		pagination.SortBy: string(pagination.Order),
	}

	output, err := json.Marshal(dsl)
	if err != nil {
		return "", err
	}

	return string(output), nil
}
<commit_msg>Avoid nil sources<commit_after>// Copyright 2016, RadiantBlue Technologies, 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 logger

import (
	"encoding/json"
	_ "fmt"
	"log"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/venicegeo/pz-gocommon/elasticsearch"
	"github.com/venicegeo/pz-gocommon/gocommon"
)

type LockedAdminStats struct {
	sync.Mutex
	LoggerAdminStats
}

type LogData struct {
	sync.Mutex
	esIndex elasticsearch.IIndex
	id      int
}

const schema = "LogData5"

type Service struct {
	stats   LockedAdminStats
	logData LogData
	origin  string
}

func (service *Service) Init(sys *piazza.SystemConfig, esIndex elasticsearch.IIndex) error {
	var err error

	service.stats.CreatedOn = time.Now()
	/***
	err = esIndex.Delete()
	if err != nil {
		log.Fatal(err)
	}
	if esIndex.IndexExists() {
		log.Fatal("index still exists")
	}
	err = esIndex.Create()
	if err != nil {
		log.Fatal(err)
	}
	***/

	if !esIndex.IndexExists() {
		log.Printf("Creating index: %s", esIndex.IndexName())
		err = esIndex.Create("")
		if err != nil {
			log.Fatal(err)
		}
	}

	if !esIndex.TypeExists(schema) {
		log.Printf("Creating type: %s", schema)

		mapping :=
			`{
			"LogData5":{
				"properties":{
					"service":{
						"type": "string",
						"store": true
					},
					"address":{
						"type": "string",
						"store": true
					},
					"createdOn":{
						"type": "date",
						"store": true
					},
					"severity":{
						"type": "string",
						"store": true
					},
					"message":{
						"type": "string",
						"store": true
					}
				}
			}
		}`

		err = esIndex.SetMapping(schema, piazza.JsonString(mapping))
		if err != nil {
			log.Printf("LoggerService.Init: %s", err.Error())
			return err
		}
	}

	service.logData.esIndex = esIndex

	service.origin = string(sys.Name)

	return nil
}

func (service *Service) newInternalErrorResponse(err error) *piazza.JsonResponse {
	return &piazza.JsonResponse{
		StatusCode: http.StatusInternalServerError,
		Message:    err.Error(),
		Origin:     service.origin,
	}
}

func (service *Service) newBadRequestResponse(err error) *piazza.JsonResponse {
	return &piazza.JsonResponse{
		StatusCode: http.StatusBadRequest,
		Message:    err.Error(),
		Origin:     service.origin,
	}
}

func (service *Service) GetRoot() *piazza.JsonResponse {
	resp := &piazza.JsonResponse{
		StatusCode: 200,
		Data:       "Hi. I'm pz-logger.",
	}

	err := resp.SetType()
	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	return resp
}

func (service *Service) PostMessage(mssg *Message) *piazza.JsonResponse {
	err := mssg.Validate()
	if err != nil {
		return service.newBadRequestResponse(err)
	}

	service.logData.Lock()
	idStr := strconv.Itoa(service.logData.id)
	service.logData.id++
	service.logData.Unlock()

	_, err = service.logData.esIndex.PostData(schema, idStr, mssg)
	if err != nil {
		//log.Printf("POST failed (1): %#v %#v", err, indexResult)
		return service.newInternalErrorResponse(err)
	}

	/*	if !indexResult.Created {
		log.Printf("POST failed (2): %#v", *indexResult)
		return NewInternalErrorResponse(err)
	}*/

	service.stats.LoggerAdminStats.NumMessages++

	resp := &piazza.JsonResponse{
		StatusCode: http.StatusOK,
		Data:       mssg,
	}

	err = resp.SetType()
	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	return resp
}

func (service *Service) GetStats() *piazza.JsonResponse {
	service.logData.Lock()
	t := service.stats.LoggerAdminStats
	service.logData.Unlock()

	resp := &piazza.JsonResponse{
		StatusCode: http.StatusOK,
		Data:       t,
	}

	err := resp.SetType()
	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	return resp
}

func (service *Service) GetMessage(params *piazza.HttpQueryParams) *piazza.JsonResponse {
	var err error

	defaultPagination := &piazza.JsonPagination{
		PerPage: 10,
		Page:    0,
		Order:   piazza.PaginationOrderDescending,
		SortBy:  "createdOn",
	}
	pagination, err := piazza.NewJsonPagination(params, defaultPagination)
	if err != nil {
		return service.newBadRequestResponse(err)
	}

	dsl, err := createQueryDslAsString(pagination, params)
	if err != nil {
		return service.newBadRequestResponse(err)
	}

	log.Printf("dsl: %s", dsl)

	var searchResult *elasticsearch.SearchResult

	if dsl == "" {
		searchResult, err = service.logData.esIndex.FilterByMatchAll(schema, pagination)
	} else {
		searchResult, err = service.logData.esIndex.SearchByJSON(schema, dsl)
	}

	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	// TODO: unsafe truncation
	matched := int(searchResult.NumberMatched())
	var lines []Message

	for _, hit := range *searchResult.GetHits() {
		if hit.Source == nil {
			log.Printf("null source hit")
			continue
		}

		var msg *Message
		err = json.Unmarshal(*hit.Source, msg)
		if err != nil {
			log.Printf("UNABLE TO PARSE: %s", string(*hit.Source))
			return service.newInternalErrorResponse(err)
		}

		// just in case
		err = msg.Validate()
		if err != nil {
			log.Printf("UNABLE TO VALIDATE: %s", string(*hit.Source))
			continue
		}

		lines = append(lines, *msg)
	}

	pagination.Count = matched
	resp := &piazza.JsonResponse{
		StatusCode: http.StatusOK,
		Data:       lines,
		Pagination: pagination,
	}

	err = resp.SetType()
	if err != nil {
		return service.newInternalErrorResponse(err)
	}

	return resp
}

func createQueryDslAsString(
	pagination *piazza.JsonPagination,
	params *piazza.HttpQueryParams) (string, error) {

	must := []map[string]interface{}{}

	service, err := params.AsString("service", nil)
	if err != nil {
		return "", err
	}

	contains, err := params.AsString("contains", nil)
	if err != nil {
		return "", err
	}

	before, err := params.AsTime("before", nil)
	if err != nil {
		return "", err
	}

	after, err := params.AsTime("after", nil)
	if err != nil {
		return "", err
	}

	if service != nil {
		must = append(must, map[string]interface{}{
			"match": map[string]interface{}{
				"service": *service,
			},
		})
	}

	if contains != nil {
		must = append(must, map[string]interface{}{
			"multi_match": map[string]interface{}{
				"query":  contains,
				"fields": []string{"address", "message", "service", "severity"},
			},
		})
	}

	if after != nil || before != nil {
		rangeParams := map[string]time.Time{}

		if after != nil {
			rangeParams["gte"] = *after
		}

		if before != nil {
			rangeParams["lte"] = *before
		}

		must = append(must, map[string]interface{}{
			"range": map[string]interface{}{
				"createdOn": rangeParams,
			},
		})
	}

	if len(must) == 0 {
		return "", nil
	}

	dsl := map[string]interface{}{
		"query": map[string]interface{}{
			"filtered": map[string]interface{}{
				"query": map[string]interface{}{
					"bool": map[string]interface{}{
						"must": must,
					},
				},
			},
		},
		"size": pagination.PerPage,
		"from": pagination.PerPage * pagination.Page,
	}

	dsl["sort"] = map[string]string{
		pagination.SortBy: string(pagination.Order),
	}

	output, err := json.Marshal(dsl)
	if err != nil {
		return "", err
	}

	return string(output), nil
}
<|endoftext|>


<commit_before>package logging

import (
	"fmt"
	"net"
	"sync"
)

const (
	socketPath = "./ctop.sock"
	socketAddr = "0.0.0.0:9000"
)

var server struct {
	wg sync.WaitGroup
	ln net.Listener
}

func getListener() net.Listener {
	var ln net.Listener
	var err error
	if debugModeTCP() {
		ln, err = net.Listen("tcp", socketAddr)
	} else {
		ln, err = net.Listen("unix", socketPath)
	}
	if err != nil {
		panic(err)
	}
	return ln
}

func StartServer() {
	server.ln = getListener()

	go func() {
		for {
			conn, err := server.ln.Accept()
			if err != nil {
				if err, ok := err.(net.Error); ok && err.Temporary() {
					continue
				}
				return
			}
			go handler(conn)
		}
	}()

	Log.Notice("logging server started")
}

func StopServer() {
	server.wg.Wait()
	if server.ln != nil {
		server.ln.Close()
	}
}

func handler(conn net.Conn) {
	server.wg.Add(1)
	defer server.wg.Done()
	defer conn.Close()
	for msg := range Log.tail() {
		msg = fmt.Sprintf("%s\n", msg)
		conn.Write([]byte(msg))
	}
	conn.Write([]byte("bye\n"))
}
<commit_msg>depend on io.WriteCloser instead of net.Conn<commit_after>package logging

import (
	"fmt"
	"io"
	"net"
	"sync"
)

const (
	socketPath = "./ctop.sock"
	socketAddr = "0.0.0.0:9000"
)

var server struct {
	wg sync.WaitGroup
	ln net.Listener
}

func getListener() net.Listener {
	var ln net.Listener
	var err error
	if debugModeTCP() {
		ln, err = net.Listen("tcp", socketAddr)
	} else {
		ln, err = net.Listen("unix", socketPath)
	}
	if err != nil {
		panic(err)
	}
	return ln
}

func StartServer() {
	server.ln = getListener()

	go func() {
		for {
			conn, err := server.ln.Accept()
			if err != nil {
				if err, ok := err.(net.Error); ok && err.Temporary() {
					continue
				}
				return
			}
			go handler(conn)
		}
	}()

	Log.Notice("logging server started")
}

func StopServer() {
	server.wg.Wait()
	if server.ln != nil {
		server.ln.Close()
	}
}

func handler(wc io.WriteCloser) {
	server.wg.Add(1)
	defer server.wg.Done()
	defer wc.Close()
	for msg := range Log.tail() {
		msg = fmt.Sprintf("%s\n", msg)
		wc.Write([]byte(msg))
	}
	wc.Write([]byte("bye\n"))
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"sync"
	"syscall"
	"time"

	"github.com/gorilla/websocket"
	"golang.org/x/sys/unix"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/response"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/logging"
	"github.com/lxc/lxd/shared/netutils"
)

const execWSControl = -1
const execWSStdin = 0
const execWSStdout = 1
const execWSStderr = 2

var execCmd = APIEndpoint{
	Name: "exec",
	Path: "exec",

	Post: APIEndpointAction{Handler: execPost},
}

func execPost(d *Daemon, r *http.Request) response.Response {
	post := api.ContainerExecPost{}

	buf, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return response.BadRequest(err)
	}

	if err := json.Unmarshal(buf, &post); err != nil {
		return response.BadRequest(err)
	}

	if !post.WaitForWS {
		return response.BadRequest(fmt.Errorf("Websockets are required for VM exec"))
	}

	env := map[string]string{}

	if post.Environment != nil {
		for k, v := range post.Environment {
			env[k] = v
		}
	}

	// Set default value for PATH
	_, ok := env["PATH"]
	if !ok {
		env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
	}

	if shared.PathExists("/snap/bin") {
		env["PATH"] = fmt.Sprintf("%s:/snap/bin", env["PATH"])
	}

	// If running as root, set some env variables
	if post.User == 0 {
		// Set default value for HOME
		_, ok = env["HOME"]
		if !ok {
			env["HOME"] = "/root"
		}

		// Set default value for USER
		_, ok = env["USER"]
		if !ok {
			env["USER"] = "root"
		}
	}

	// Set default value for LANG
	_, ok = env["LANG"]
	if !ok {
		env["LANG"] = "C.UTF-8"
	}

	// Set the default working directory
	if post.Cwd == "" {
		post.Cwd = env["HOME"]
		if post.Cwd == "" {
			post.Cwd = "/"
		}
	}

	ws := &execWs{}
	ws.fds = map[int]string{}

	ws.conns = map[int]*websocket.Conn{}
	ws.conns[execWSControl] = nil
	ws.conns[0] = nil // This is used for either TTY or Stdin.
	if !post.Interactive {
		ws.conns[execWSStdout] = nil
		ws.conns[execWSStderr] = nil
	}
	ws.requiredConnectedCtx, ws.requiredConnectedDone = context.WithCancel(context.Background())
	ws.interactive = post.Interactive

	for i := range ws.conns {
		ws.fds[i], err = shared.RandomCryptoString()
		if err != nil {
			return response.InternalError(err)
		}
	}

	ws.command = post.Command
	ws.env = env

	ws.width = post.Width
	ws.height = post.Height

	ws.cwd = post.Cwd
	ws.uid = post.User
	ws.gid = post.Group

	resources := map[string][]string{}

	op, err := operations.OperationCreate(nil, "", operations.OperationClassWebsocket, db.OperationCommandExec, resources, ws.Metadata(), ws.Do, nil, ws.Connect, r)
	if err != nil {
		return response.InternalError(err)
	}

	// Link the operation to the agent's event server.
	op.SetEventServer(d.events)

	return operations.OperationResponse(op)
}

type execWs struct {
	command               []string
	env                   map[string]string
	conns                 map[int]*websocket.Conn
	connsLock             sync.Mutex
	requiredConnectedCtx  context.Context
	requiredConnectedDone func()
	interactive           bool
	fds                   map[int]string
	width                 int
	height                int
	uid                   uint32
	gid                   uint32
	cwd                   string
}

func (s *execWs) Metadata() interface{} {
	fds := shared.Jmap{}
	for fd, secret := range s.fds {
		if fd == execWSControl {
			fds["control"] = secret
		} else {
			fds[strconv.Itoa(fd)] = secret
		}
	}

	return shared.Jmap{
		"fds":         fds,
		"command":     s.command,
		"environment": s.env,
		"interactive": s.interactive,
	}
}

func (s *execWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
	secret := r.FormValue("secret")
	if secret == "" {
		return fmt.Errorf("missing secret")
	}

	for fd, fdSecret := range s.fds {
		if secret == fdSecret {
			conn, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
			if err != nil {
				return err
			}

			s.connsLock.Lock()
			defer s.connsLock.Unlock()

			val, found := s.conns[fd]
			if found && val == nil {
				s.conns[fd] = conn

				for _, c := range s.conns {
					if c == nil {
						return nil // Not all required connections connected yet.
					}
				}

				s.requiredConnectedDone() // All required connections now connected.
				return nil
			} else if !found {
				return fmt.Errorf("Unknown websocket number")
			} else {
				return fmt.Errorf("Websocket number already connected")
			}
		}
	}

	/* If we didn't find the right secret, the user provided a bad one,
	 * which 403, not 404, since this Operation actually exists */
	return os.ErrPermission
}

func (s *execWs) Do(op *operations.Operation) error {
	// Once this function ends ensure that any connected websockets are closed.
	defer func() {
		s.connsLock.Lock()
		for i := range s.conns {
			if s.conns[i] != nil {
				s.conns[i].Close()
			}
		}
		s.connsLock.Unlock()
	}()

	// As this function only gets called when the exec request has WaitForWS enabled, we expect the client to
	// connect to all of the required websockets within a short period of time and we won't proceed until then.
	logger.Debug("Waiting for exec websockets to connect")
	select {
	case <-s.requiredConnectedCtx.Done():
		break
	case <-time.After(time.Second * 5):
		return fmt.Errorf("Timed out waiting for websockets to connect")
	}

	var err error
	var ttys []*os.File
	var ptys []*os.File

	var stdin *os.File
	var stdout *os.File
	var stderr *os.File

	if s.interactive {
		ttys = make([]*os.File, 1)
		ptys = make([]*os.File, 1)
		ptys[0], ttys[0], err = shared.OpenPty(int64(s.uid), int64(s.gid))
		if err != nil {
			return err
		}

		stdin = ttys[0]
		stdout = ttys[0]
		stderr = ttys[0]

		if s.width > 0 && s.height > 0 {
			shared.SetSize(int(ptys[0].Fd()), s.width, s.height)
		}
	} else {
		ttys = make([]*os.File, 3)
		ptys = make([]*os.File, 3)
		for i := 0; i < len(ttys); i++ {
			ptys[i], ttys[i], err = os.Pipe()
			if err != nil {
				return err
			}
		}

		stdin = ptys[execWSStdin]
		stdout = ttys[execWSStdout]
		stderr = ttys[execWSStderr]
	}

	attachedChildIsDead := make(chan struct{})
	var wgEOF sync.WaitGroup

	finisher := func(cmdResult int, cmdErr error) error {
		for _, tty := range ttys {
			tty.Close()
		}

		s.connsLock.Lock()
		conn := s.conns[-1]
		s.connsLock.Unlock()

		if conn != nil {
			conn.Close() // Close control connection (will cause control go routine to end).
		}

		close(attachedChildIsDead)

		wgEOF.Wait()

		for _, pty := range ptys {
			pty.Close()
		}

		metadata := shared.Jmap{"return": cmdResult}
		err = op.UpdateMetadata(metadata)
		if err != nil {
			return err
		}

		return cmdErr
	}

	var cmd *exec.Cmd

	if len(s.command) > 1 {
		cmd = exec.Command(s.command[0], s.command[1:]...)
	} else {
		cmd = exec.Command(s.command[0])
	}

	// Prepare the environment
	for k, v := range s.env {
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
	}

	cmd.Stdin = stdin
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Credential: &syscall.Credential{
			Uid: s.uid,
			Gid: s.gid,
		},
		// Creates a new session if the calling process is not a process group leader.
		// The calling process is the leader of the new session, the process group leader of
		// the new process group, and has no controlling terminal.
		// This is important to allow remote shells to handle ctrl+c.
		Setsid: true,
	}

	// Make the given terminal the controlling terminal of the calling process.
	// The calling process must be a session leader and not have a controlling terminal already.
	// This is important as allows ctrl+c to work as expected for non-shell programs.
	if s.interactive {
		cmd.SysProcAttr.Setctty = true
	}

	cmd.Dir = s.cwd

	err = cmd.Start()
	if err != nil {
		exitStatus := -1

		if errors.Is(err, exec.ErrNotFound) {
			exitStatus = 127
			err = nil // Allow the exit code to be returned.
		}

		return finisher(exitStatus, err)
	}

	logger := logging.AddContext(logger.Log, log.Ctx{"PID": cmd.Process.Pid, "interactive": s.interactive})
	logger.Debug("Instance process started")

	wgEOF.Add(1)
	go func() {
		defer wgEOF.Done()

		logger.Debug("Exec control handler started")
		defer logger.Debug("Exec control handler finished")

		s.connsLock.Lock()
		conn := s.conns[-1]
		s.connsLock.Unlock()

		for {
			mt, r, err := conn.NextReader()
			if mt == websocket.CloseMessage {
				break
			}

			if err != nil {
				logger.Debug("Got error getting next reader", log.Ctx{"err": err})
				er, ok := err.(*websocket.CloseError)
				if !ok {
					break
				}

				if er.Code != websocket.CloseAbnormalClosure {
					break
				}

				// If an abnormal closure occurred, kill the attached process.
				err := unix.Kill(cmd.Process.Pid, unix.SIGKILL)
				if err != nil {
					logger.Error("Failed to send SIGKILL")
				} else {
					logger.Info("Sent SIGKILL")
				}
				return
			}

			buf, err := ioutil.ReadAll(r)
			if err != nil {
				logger.Debug("Failed to read message", log.Ctx{"err": err})
				break
			}

			command := api.ContainerExecControl{}

			if err := json.Unmarshal(buf, &command); err != nil {
				logger.Debug("Failed to unmarshal control socket command", log.Ctx{"err": err})
				continue
			}

			if command.Command == "window-resize" && s.interactive {
				winchWidth, err := strconv.Atoi(command.Args["width"])
				if err != nil {
					logger.Debug("Unable to extract window width", log.Ctx{"err": err})
					continue
				}

				winchHeight, err := strconv.Atoi(command.Args["height"])
				if err != nil {
					logger.Debug("Unable to extract window height", log.Ctx{"err": err})
					continue
				}

				err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight)
				if err != nil {
					logger.Debug("Failed to set window size", log.Ctx{"err": err, "width": winchWidth, "height": winchHeight})
					continue
				}
			} else if command.Command == "signal" {
				if err := unix.Kill(cmd.Process.Pid, unix.Signal(command.Signal)); err != nil {
					logger.Debug("Failed forwarding signal", log.Ctx{"err": err, "signal": command.Signal})
					continue
				}
				logger.Info("Forwarded signal", log.Ctx{"signal": command.Signal})
			}
		}
	}()

	if s.interactive {
		wgEOF.Add(1)
		go func() {
			defer wgEOF.Done()

			logger.Debug("Exec mirror websocket started", log.Ctx{"number": 0})
			defer logger.Debug("Exec mirror websocket finished", log.Ctx{"number": 0})

			s.connsLock.Lock()
			conn := s.conns[0]
			s.connsLock.Unlock()

			readDone, writeDone := netutils.WebsocketExecMirror(conn, ptys[0], ptys[0], attachedChildIsDead, int(ptys[0].Fd()))

			<-readDone
			<-writeDone
			conn.Close()
		}()
	} else {
		wgEOF.Add(len(ttys) - 1)
		for i := 0; i < len(ttys); i++ {
			go func(i int) {
				logger.Debug("Exec mirror websocket started", log.Ctx{"number": i})
				defer logger.Debug("Exec mirror websocket finished", log.Ctx{"number": i})

				if i == 0 {
					s.connsLock.Lock()
					conn := s.conns[i]
					s.connsLock.Unlock()

					<-shared.WebsocketRecvStream(ttys[i], conn)
					ttys[i].Close()
				} else {
					s.connsLock.Lock()
					conn := s.conns[i]
					s.connsLock.Unlock()

					<-shared.WebsocketSendStream(conn, ptys[i], -1)
					ptys[i].Close()
					wgEOF.Done()
				}
			}(i)
		}
	}

	exitStatus, err := shared.ExitStatus(cmd.Wait())

	logger.Debug("Instance process stopped", log.Ctx{"exitStatus": exitStatus})
	return finisher(exitStatus, nil)
}
<commit_msg>lxd-agent/exec: Close attachedChildIsDead channel first in finisher<commit_after>package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"sync"
	"syscall"
	"time"

	"github.com/gorilla/websocket"
	"golang.org/x/sys/unix"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/response"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/lxc/lxd/shared/logging"
	"github.com/lxc/lxd/shared/netutils"
)

const execWSControl = -1
const execWSStdin = 0
const execWSStdout = 1
const execWSStderr = 2

var execCmd = APIEndpoint{
	Name: "exec",
	Path: "exec",

	Post: APIEndpointAction{Handler: execPost},
}

func execPost(d *Daemon, r *http.Request) response.Response {
	post := api.ContainerExecPost{}

	buf, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return response.BadRequest(err)
	}

	if err := json.Unmarshal(buf, &post); err != nil {
		return response.BadRequest(err)
	}

	if !post.WaitForWS {
		return response.BadRequest(fmt.Errorf("Websockets are required for VM exec"))
	}

	env := map[string]string{}

	if post.Environment != nil {
		for k, v := range post.Environment {
			env[k] = v
		}
	}

	// Set default value for PATH
	_, ok := env["PATH"]
	if !ok {
		env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
	}

	if shared.PathExists("/snap/bin") {
		env["PATH"] = fmt.Sprintf("%s:/snap/bin", env["PATH"])
	}

	// If running as root, set some env variables
	if post.User == 0 {
		// Set default value for HOME
		_, ok = env["HOME"]
		if !ok {
			env["HOME"] = "/root"
		}

		// Set default value for USER
		_, ok = env["USER"]
		if !ok {
			env["USER"] = "root"
		}
	}

	// Set default value for LANG
	_, ok = env["LANG"]
	if !ok {
		env["LANG"] = "C.UTF-8"
	}

	// Set the default working directory
	if post.Cwd == "" {
		post.Cwd = env["HOME"]
		if post.Cwd == "" {
			post.Cwd = "/"
		}
	}

	ws := &execWs{}
	ws.fds = map[int]string{}

	ws.conns = map[int]*websocket.Conn{}
	ws.conns[execWSControl] = nil
	ws.conns[0] = nil // This is used for either TTY or Stdin.
	if !post.Interactive {
		ws.conns[execWSStdout] = nil
		ws.conns[execWSStderr] = nil
	}
	ws.requiredConnectedCtx, ws.requiredConnectedDone = context.WithCancel(context.Background())
	ws.interactive = post.Interactive

	for i := range ws.conns {
		ws.fds[i], err = shared.RandomCryptoString()
		if err != nil {
			return response.InternalError(err)
		}
	}

	ws.command = post.Command
	ws.env = env

	ws.width = post.Width
	ws.height = post.Height

	ws.cwd = post.Cwd
	ws.uid = post.User
	ws.gid = post.Group

	resources := map[string][]string{}

	op, err := operations.OperationCreate(nil, "", operations.OperationClassWebsocket, db.OperationCommandExec, resources, ws.Metadata(), ws.Do, nil, ws.Connect, r)
	if err != nil {
		return response.InternalError(err)
	}

	// Link the operation to the agent's event server.
	op.SetEventServer(d.events)

	return operations.OperationResponse(op)
}

type execWs struct {
	command               []string
	env                   map[string]string
	conns                 map[int]*websocket.Conn
	connsLock             sync.Mutex
	requiredConnectedCtx  context.Context
	requiredConnectedDone func()
	interactive           bool
	fds                   map[int]string
	width                 int
	height                int
	uid                   uint32
	gid                   uint32
	cwd                   string
}

func (s *execWs) Metadata() interface{} {
	fds := shared.Jmap{}
	for fd, secret := range s.fds {
		if fd == execWSControl {
			fds["control"] = secret
		} else {
			fds[strconv.Itoa(fd)] = secret
		}
	}

	return shared.Jmap{
		"fds":         fds,
		"command":     s.command,
		"environment": s.env,
		"interactive": s.interactive,
	}
}

func (s *execWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
	secret := r.FormValue("secret")
	if secret == "" {
		return fmt.Errorf("missing secret")
	}

	for fd, fdSecret := range s.fds {
		if secret == fdSecret {
			conn, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
			if err != nil {
				return err
			}

			s.connsLock.Lock()
			defer s.connsLock.Unlock()

			val, found := s.conns[fd]
			if found && val == nil {
				s.conns[fd] = conn

				for _, c := range s.conns {
					if c == nil {
						return nil // Not all required connections connected yet.
					}
				}

				s.requiredConnectedDone() // All required connections now connected.
				return nil
			} else if !found {
				return fmt.Errorf("Unknown websocket number")
			} else {
				return fmt.Errorf("Websocket number already connected")
			}
		}
	}

	/* If we didn't find the right secret, the user provided a bad one,
	 * which 403, not 404, since this Operation actually exists */
	return os.ErrPermission
}

func (s *execWs) Do(op *operations.Operation) error {
	// Once this function ends ensure that any connected websockets are closed.
	defer func() {
		s.connsLock.Lock()
		for i := range s.conns {
			if s.conns[i] != nil {
				s.conns[i].Close()
			}
		}
		s.connsLock.Unlock()
	}()

	// As this function only gets called when the exec request has WaitForWS enabled, we expect the client to
	// connect to all of the required websockets within a short period of time and we won't proceed until then.
	logger.Debug("Waiting for exec websockets to connect")
	select {
	case <-s.requiredConnectedCtx.Done():
		break
	case <-time.After(time.Second * 5):
		return fmt.Errorf("Timed out waiting for websockets to connect")
	}

	var err error
	var ttys []*os.File
	var ptys []*os.File

	var stdin *os.File
	var stdout *os.File
	var stderr *os.File

	if s.interactive {
		ttys = make([]*os.File, 1)
		ptys = make([]*os.File, 1)
		ptys[0], ttys[0], err = shared.OpenPty(int64(s.uid), int64(s.gid))
		if err != nil {
			return err
		}

		stdin = ttys[0]
		stdout = ttys[0]
		stderr = ttys[0]

		if s.width > 0 && s.height > 0 {
			shared.SetSize(int(ptys[0].Fd()), s.width, s.height)
		}
	} else {
		ttys = make([]*os.File, 3)
		ptys = make([]*os.File, 3)
		for i := 0; i < len(ttys); i++ {
			ptys[i], ttys[i], err = os.Pipe()
			if err != nil {
				return err
			}
		}

		stdin = ptys[execWSStdin]
		stdout = ttys[execWSStdout]
		stderr = ttys[execWSStderr]
	}

	attachedChildIsDead := make(chan struct{})
	var wgEOF sync.WaitGroup

	finisher := func(cmdResult int, cmdErr error) error {
		// Close this before closing the control connection so control handler can detect command ending.
		close(attachedChildIsDead)

		for _, tty := range ttys {
			tty.Close()
		}

		s.connsLock.Lock()
		conn := s.conns[-1]
		s.connsLock.Unlock()

		if conn != nil {
			conn.Close() // Close control connection (will cause control go routine to end).
		}

		wgEOF.Wait()

		for _, pty := range ptys {
			pty.Close()
		}

		metadata := shared.Jmap{"return": cmdResult}
		err = op.UpdateMetadata(metadata)
		if err != nil {
			return err
		}

		return cmdErr
	}

	var cmd *exec.Cmd

	if len(s.command) > 1 {
		cmd = exec.Command(s.command[0], s.command[1:]...)
	} else {
		cmd = exec.Command(s.command[0])
	}

	// Prepare the environment
	for k, v := range s.env {
		cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", k, v))
	}

	cmd.Stdin = stdin
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	cmd.SysProcAttr = &syscall.SysProcAttr{
		Credential: &syscall.Credential{
			Uid: s.uid,
			Gid: s.gid,
		},
		// Creates a new session if the calling process is not a process group leader.
		// The calling process is the leader of the new session, the process group leader of
		// the new process group, and has no controlling terminal.
		// This is important to allow remote shells to handle ctrl+c.
		Setsid: true,
	}

	// Make the given terminal the controlling terminal of the calling process.
	// The calling process must be a session leader and not have a controlling terminal already.
	// This is important as allows ctrl+c to work as expected for non-shell programs.
	if s.interactive {
		cmd.SysProcAttr.Setctty = true
	}

	cmd.Dir = s.cwd

	err = cmd.Start()
	if err != nil {
		exitStatus := -1

		if errors.Is(err, exec.ErrNotFound) {
			exitStatus = 127
			err = nil // Allow the exit code to be returned.
		}

		return finisher(exitStatus, err)
	}

	logger := logging.AddContext(logger.Log, log.Ctx{"PID": cmd.Process.Pid, "interactive": s.interactive})
	logger.Debug("Instance process started")

	wgEOF.Add(1)
	go func() {
		defer wgEOF.Done()

		logger.Debug("Exec control handler started")
		defer logger.Debug("Exec control handler finished")

		s.connsLock.Lock()
		conn := s.conns[-1]
		s.connsLock.Unlock()

		for {
			mt, r, err := conn.NextReader()
			if mt == websocket.CloseMessage {
				break
			}

			if err != nil {
				logger.Debug("Got error getting next reader", log.Ctx{"err": err})
				er, ok := err.(*websocket.CloseError)
				if !ok {
					break
				}

				if er.Code != websocket.CloseAbnormalClosure {
					break
				}

				// If an abnormal closure occurred, kill the attached process.
				err := unix.Kill(cmd.Process.Pid, unix.SIGKILL)
				if err != nil {
					logger.Error("Failed to send SIGKILL")
				} else {
					logger.Info("Sent SIGKILL")
				}
				return
			}

			buf, err := ioutil.ReadAll(r)
			if err != nil {
				logger.Debug("Failed to read message", log.Ctx{"err": err})
				break
			}

			command := api.ContainerExecControl{}

			if err := json.Unmarshal(buf, &command); err != nil {
				logger.Debug("Failed to unmarshal control socket command", log.Ctx{"err": err})
				continue
			}

			if command.Command == "window-resize" && s.interactive {
				winchWidth, err := strconv.Atoi(command.Args["width"])
				if err != nil {
					logger.Debug("Unable to extract window width", log.Ctx{"err": err})
					continue
				}

				winchHeight, err := strconv.Atoi(command.Args["height"])
				if err != nil {
					logger.Debug("Unable to extract window height", log.Ctx{"err": err})
					continue
				}

				err = shared.SetSize(int(ptys[0].Fd()), winchWidth, winchHeight)
				if err != nil {
					logger.Debug("Failed to set window size", log.Ctx{"err": err, "width": winchWidth, "height": winchHeight})
					continue
				}
			} else if command.Command == "signal" {
				if err := unix.Kill(cmd.Process.Pid, unix.Signal(command.Signal)); err != nil {
					logger.Debug("Failed forwarding signal", log.Ctx{"err": err, "signal": command.Signal})
					continue
				}
				logger.Info("Forwarded signal", log.Ctx{"signal": command.Signal})
			}
		}
	}()

	if s.interactive {
		wgEOF.Add(1)
		go func() {
			defer wgEOF.Done()

			logger.Debug("Exec mirror websocket started", log.Ctx{"number": 0})
			defer logger.Debug("Exec mirror websocket finished", log.Ctx{"number": 0})

			s.connsLock.Lock()
			conn := s.conns[0]
			s.connsLock.Unlock()

			readDone, writeDone := netutils.WebsocketExecMirror(conn, ptys[0], ptys[0], attachedChildIsDead, int(ptys[0].Fd()))

			<-readDone
			<-writeDone
			conn.Close()
		}()
	} else {
		wgEOF.Add(len(ttys) - 1)
		for i := 0; i < len(ttys); i++ {
			go func(i int) {
				logger.Debug("Exec mirror websocket started", log.Ctx{"number": i})
				defer logger.Debug("Exec mirror websocket finished", log.Ctx{"number": i})

				if i == 0 {
					s.connsLock.Lock()
					conn := s.conns[i]
					s.connsLock.Unlock()

					<-shared.WebsocketRecvStream(ttys[i], conn)
					ttys[i].Close()
				} else {
					s.connsLock.Lock()
					conn := s.conns[i]
					s.connsLock.Unlock()

					<-shared.WebsocketSendStream(conn, ptys[i], -1)
					ptys[i].Close()
					wgEOF.Done()
				}
			}(i)
		}
	}

	exitStatus, err := shared.ExitStatus(cmd.Wait())

	logger.Debug("Instance process stopped", log.Ctx{"exitStatus": exitStatus})
	return finisher(exitStatus, nil)
}
<|endoftext|>


<commit_before>package device

import (
	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/shared"
)

// nicTypes defines the supported nic type devices and defines their creation functions.
var nicTypes = map[string]func() device{
	"physical": func() device { return &nicPhysical{} },
	"ipvlan":   func() device { return &nicIPVLAN{} },
	"p2p":      func() device { return &nicP2P{} },
	"bridged":  func() device { return &nicBridged{} },
	"routed":   func() device { return &nicRouted{} },
	"macvlan":  func() device { return &nicMACVLAN{} },
	"sriov":    func() device { return &nicSRIOV{} },
}

// nicLoadByType returns a NIC device instantiated with supplied config.
func nicLoadByType(c deviceConfig.Device) device {
	f := nicTypes[c.NICType()]
	if f != nil {
		return f()
	}
	return nil
}

// nicValidationRules returns config validation rules for nic devices.
func nicValidationRules(requiredFields []string, optionalFields []string) map[string]func(value string) error {
	// Define a set of default validators for each field name.
	defaultValidators := map[string]func(value string) error{
		"name":                    shared.IsAny,
		"parent":                  shared.IsAny,
		"network":                 shared.IsAny,
		"mtu":                     shared.IsAny,
		"vlan":                    shared.IsAny,
		"hwaddr":                  networkValidMAC,
		"host_name":               shared.IsAny,
		"limits.ingress":          shared.IsAny,
		"limits.egress":           shared.IsAny,
		"limits.max":              shared.IsAny,
		"security.mac_filtering":  shared.IsAny,
		"security.ipv4_filtering": shared.IsAny,
		"security.ipv6_filtering": shared.IsAny,
		"maas.subnet.ipv4":        shared.IsAny,
		"maas.subnet.ipv6":        shared.IsAny,
		"ipv4.address":            NetworkValidAddressV4,
		"ipv6.address":            NetworkValidAddressV6,
		"ipv4.routes":             NetworkValidNetworkV4List,
		"ipv6.routes":             NetworkValidNetworkV6List,
		"boot.priority":           shared.IsUint32,
		"ipv4.gateway":            NetworkValidGateway,
		"ipv6.gateway":            NetworkValidGateway,
	}

	validators := map[string]func(value string) error{}

	for _, k := range optionalFields {
		defaultValidator := defaultValidators[k]

		// If field doesn't have a known validator, it is an unknown field, skip.
		if defaultValidator == nil {
			continue
		}

		// Wrap the default validator in an empty check as field is optional.
		validators[k] = func(value string) error {
			if value == "" {
				return nil
			}

			return defaultValidator(value)
		}
	}

	// Add required fields last, that way if they are specified in both required and optional
	// field sets, the required one will overwrite the optional validators.
	for _, k := range requiredFields {
		defaultValidator := defaultValidators[k]

		// If field doesn't have a known validator, it is an unknown field, skip.
		if defaultValidator == nil {
			continue
		}

		// Wrap the default validator in a not empty check as field is required.
		validators[k] = func(value string) error {
			err := shared.IsNotEmpty(value)
			if err != nil {
				return err
			}

			return defaultValidator(value)
		}
	}

	return validators
}

// nicHasAutoGateway takes the value of the "ipv4.gateway" or "ipv6.gateway" config keys and returns whether they
// specify whether the gateway mode is automatic or not
func nicHasAutoGateway(value string) bool {
	if value == "" || value == "auto" {
		return true
	}

	return false
}
<commit_msg>lxd/device/nic: Adds ipv4.host_address and ipv6.host_address keys<commit_after>package device

import (
	deviceConfig "github.com/lxc/lxd/lxd/device/config"
	"github.com/lxc/lxd/shared"
)

// nicTypes defines the supported nic type devices and defines their creation functions.
var nicTypes = map[string]func() device{
	"physical": func() device { return &nicPhysical{} },
	"ipvlan":   func() device { return &nicIPVLAN{} },
	"p2p":      func() device { return &nicP2P{} },
	"bridged":  func() device { return &nicBridged{} },
	"routed":   func() device { return &nicRouted{} },
	"macvlan":  func() device { return &nicMACVLAN{} },
	"sriov":    func() device { return &nicSRIOV{} },
}

// nicLoadByType returns a NIC device instantiated with supplied config.
func nicLoadByType(c deviceConfig.Device) device {
	f := nicTypes[c.NICType()]
	if f != nil {
		return f()
	}
	return nil
}

// nicValidationRules returns config validation rules for nic devices.
func nicValidationRules(requiredFields []string, optionalFields []string) map[string]func(value string) error {
	// Define a set of default validators for each field name.
	defaultValidators := map[string]func(value string) error{
		"name":                    shared.IsAny,
		"parent":                  shared.IsAny,
		"network":                 shared.IsAny,
		"mtu":                     shared.IsAny,
		"vlan":                    shared.IsAny,
		"hwaddr":                  networkValidMAC,
		"host_name":               shared.IsAny,
		"limits.ingress":          shared.IsAny,
		"limits.egress":           shared.IsAny,
		"limits.max":              shared.IsAny,
		"security.mac_filtering":  shared.IsAny,
		"security.ipv4_filtering": shared.IsAny,
		"security.ipv6_filtering": shared.IsAny,
		"maas.subnet.ipv4":        shared.IsAny,
		"maas.subnet.ipv6":        shared.IsAny,
		"ipv4.address":            NetworkValidAddressV4,
		"ipv6.address":            NetworkValidAddressV6,
		"ipv4.routes":             NetworkValidNetworkV4List,
		"ipv6.routes":             NetworkValidNetworkV6List,
		"boot.priority":           shared.IsUint32,
		"ipv4.gateway":            NetworkValidGateway,
		"ipv6.gateway":            NetworkValidGateway,
		"ipv4.host_address":       NetworkValidAddressV4,
		"ipv6.host_address":       NetworkValidAddressV6,
	}

	validators := map[string]func(value string) error{}

	for _, k := range optionalFields {
		defaultValidator := defaultValidators[k]

		// If field doesn't have a known validator, it is an unknown field, skip.
		if defaultValidator == nil {
			continue
		}

		// Wrap the default validator in an empty check as field is optional.
		validators[k] = func(value string) error {
			if value == "" {
				return nil
			}

			return defaultValidator(value)
		}
	}

	// Add required fields last, that way if they are specified in both required and optional
	// field sets, the required one will overwrite the optional validators.
	for _, k := range requiredFields {
		defaultValidator := defaultValidators[k]

		// If field doesn't have a known validator, it is an unknown field, skip.
		if defaultValidator == nil {
			continue
		}

		// Wrap the default validator in a not empty check as field is required.
		validators[k] = func(value string) error {
			err := shared.IsNotEmpty(value)
			if err != nil {
				return err
			}

			return defaultValidator(value)
		}
	}

	return validators
}

// nicHasAutoGateway takes the value of the "ipv4.gateway" or "ipv6.gateway" config keys and returns whether they
// specify whether the gateway mode is automatic or not
func nicHasAutoGateway(value string) bool {
	if value == "" || value == "auto" {
		return true
	}

	return false
}
<|endoftext|>


<commit_before>package system

import (
  "io"
  "io/ioutil"
  "errors"
  "fmt"
  "os"
  "net/http"
  "crypto/tls"
  "crypto/md5"
  "runtime"

  "github.com/codegangsta/cli"
  "github.com/Sirupsen/logrus"
  "github.com/kardianos/osext"
)

const binaryURL = "https://artifactory1.eng.lancope.local/generic-dev-infrastructure/lc/lc-%s-%s-%s"

// CmdUpgrade will upgrade the current lc binary
func CmdUpgrade(c *cli.Context) error {
  version := c.String("version")
  if len(version) == 0 {
    return errors.New("upgrade command requires a version argument, none found")
  }

  platform := runtime.GOOS
  arch := runtime.GOARCH
  url := fmt.Sprintf(binaryURL, platform, arch, version)
  logrus.Debugf("using url: %s", url)

  // find location of lc currently running
  lcPath, err := getLcLocation()
  if err != nil {
    logrus.Errorf("could not find location of current lc")
    return err
  }

  //create staging area to place tmp files
  tmpDir, err := ioutil.TempDir("", "lcupgrade")
  if err != nil {
    return fmt.Errorf("failed creating temp dir, cannot proceed. err: %q", err)
  }
  defer os.RemoveAll(tmpDir)

  //download new binary to staging location
  newLcTmp, err := downloadNew(tmpDir, url)
  if err != nil {
    return err
  }

  // figure out if we need the new lc
  var doSwap = detectDifferences(lcPath, newLcTmp)
  if doSwap {
    // rename current binary in preparation for replacing
    oldLcTmp, err := mvLc(tmpDir, lcPath)
    if err != nil {
      return err
    }

    // swap in new lc
    if err := swap(newLcTmp, lcPath); err != nil {
      logrus.Debugf("failed swaping new lc from %q to %q, err: %q", newLcTmp, lcPath, err)
      return fmt.Errorf("failed replacing your lc, your old binary is located at %q", oldLcTmp)
    }
    logrus.Infof("lc install finished, new lc binary installed")
  } else {
    logrus.Infof("lc install finished, lc binary was already the latest")
  }

  return nil
}

// detectDifferences will return true if the files have different md5 hashes
func detectDifferences(files ...string) bool {
  if len(files) < 2 {
    return false
  }

  md5sums := make([]string, 0, len(files))
  for _, file := range files {
    md5, err := computeMd5(file)
    if err != nil {
      logrus.Warnf("could not compute md5 for file %q", file)
      return true
    }
    md5sums = append(md5sums, md5)
  }

  for i := 1; i < len(md5sums); i++ {
    if md5sums[i] != md5sums[i-1]{
      return true
    }
  }
  return false
}

// swap will rename the src file to the dst file
func swap(src string, dst string) error {
  if err := os.Rename(src, dst); err != nil {
    logrus.Debugf("failed swapping '%s' to '%s'", src, dst, err)
    return err
  }
  return nil
}

func computeMd5(filePath string) (string, error) {
  file, err := os.Open(filePath)
  if err != nil {
    logrus.Debugf("could not open file at '%'", filePath, err)
    return "", err
  }
  defer file.Close()

  md5 := md5.New()
  if _, err := io.Copy(md5, file); err != nil {
    logrus.Debugf("could copy file at '%'", filePath, err)
    return "", err
  }

  var result []byte
  return string(md5.Sum(result)), nil
}

func getLcLocation() (string, error){
  // NOTE: if os.Args[0] is a symlink, this code will update the actual binary, not the link
  lcPath, err := osext.Executable()
  if err != nil {
    logrus.Debugf("lc not found", err)
    return "", err
  }
  return lcPath, nil
}

// move src file into a tmp file in the given 'dir'
// returns fullpath of new location
func mvLc(tmpDir string, src string) (string, error) {
  tmpLocation := fmt.Sprintf("%s/%s", tmpDir, "lc.old")
  logrus.Debugf("moving binary '%s' to '%s'", src, tmpLocation)
  if err := swap(src, tmpLocation); err != nil {
      logrus.Debugf("failed moving binary ", err)
      return "", err
  }
  return tmpLocation, nil
}

// Will download the new binary from the given url into the given 'dir'
// returns full path of binary
func downloadNew(tmpDir string, url string) (string, error) {
  tmpLocation := fmt.Sprintf("%s/%s", tmpDir, "lc.new")
  logrus.Debugf("downloading new binary to '%s'", tmpLocation)
  if err := installNew(url, tmpLocation); err != nil {
    logrus.Debugf("failed downloading binary, err: %q", err)
    return "", err
  }
  return tmpLocation, nil
}

func installNew(url string, target string) error {
  tr := &http.Transport{
      TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  }
  client := &http.Client{Transport: tr}

  resp, err := client.Get(url)
  if err != nil {
    logrus.Debugf("failed downloading binary", err)
  	return err
  }
  if resp.StatusCode != 200 {
    return fmt.Errorf("failed downloading binary, invalid http response: %d", resp.StatusCode)
  }
  defer resp.Body.Close()

  out, err := os.Create(target)
  if err != nil {
    logrus.Debugf("failed creating new lc file", err)
    return err
  }
  defer out.Close()

  if n, err := io.Copy(out, resp.Body); err != nil {
    logrus.Debugf("failed copying new lc file", err)
    return err
  } else {
    logrus.Debugf("successfully coppied %d bytes", n)
  }

  if err := os.Chmod(target, os.FileMode(0755)); err != nil {
    logrus.Debugf("failed making lc executable", err)
    return err
  }
  return nil
}
<commit_msg>cleanup debug logging<commit_after>package system

import (
  "io"
  "io/ioutil"
  "errors"
  "fmt"
  "os"
  "net/http"
  "crypto/tls"
  "crypto/md5"
  "runtime"

  "github.com/codegangsta/cli"
  "github.com/Sirupsen/logrus"
  "github.com/kardianos/osext"
)

const binaryURL = "https://artifactory1.eng.lancope.local/generic-dev-infrastructure/lc/lc-%s-%s-%s"

// CmdUpgrade will upgrade the current lc binary
func CmdUpgrade(c *cli.Context) error {
  version := c.String("version")
  if len(version) == 0 {
    return errors.New("upgrade command requires a version argument, none found")
  }

  platform := runtime.GOOS
  arch := runtime.GOARCH
  url := fmt.Sprintf(binaryURL, platform, arch, version)

  // find location of lc currently running
  lcPath, err := getLcLocation()
  if err != nil {
    logrus.Errorf("could not find location of current lc")
    return err
  }

  //create staging area to place tmp files
  tmpDir, err := ioutil.TempDir("", "lcupgrade")
  if err != nil {
    return fmt.Errorf("failed creating temp dir, cannot proceed. err: %q", err)
  }
  defer os.RemoveAll(tmpDir)

  //download new binary to staging location
  newLcTmp, err := downloadNew(tmpDir, url)
  if err != nil {
    return err
  }

  // figure out if we need the new lc
  var doSwap = detectDifferences(lcPath, newLcTmp)
  if doSwap {
    logrus.Debugf("replacing current binary with new one")
    // rename current binary in preparation for replacing
    oldLcTmp, err := mvLc(tmpDir, lcPath)
    if err != nil {
      return err
    }

    // swap in new lc
    if err := swap(newLcTmp, lcPath); err != nil {
      logrus.Debugf("failed swaping new lc from %q to %q, err: %q", newLcTmp, lcPath, err)
      return fmt.Errorf("failed replacing your lc, your old binary is located at %q", oldLcTmp)
    }
    logrus.Infof("lc install finished, new lc binary installed")
  } else {
    logrus.Infof("lc install finished, lc binary was already the latest")
  }

  return nil
}

// detectDifferences will return true if the files have different md5 hashes
func detectDifferences(files ...string) bool {
  if len(files) < 2 {
    return false
  }

  md5sums := make([]string, 0, len(files))
  for _, file := range files {
    md5, err := computeMd5(file)
    if err != nil {
      logrus.Warnf("could not compute md5 for file %q", file)
      return true
    }
    md5sums = append(md5sums, md5)
  }

  for i := 1; i < len(md5sums); i++ {
    if md5sums[i] != md5sums[i-1]{
      return true
    }
  }
  return false
}

// swap will rename the src file to the dst file
func swap(src string, dst string) error {
  if err := os.Rename(src, dst); err != nil {
    logrus.Debugf("failed swapping '%s' to '%s'", src, dst, err)
    return err
  }
  return nil
}

func computeMd5(filePath string) (string, error) {
  file, err := os.Open(filePath)
  if err != nil {
    logrus.Debugf("could not open file at '%'", filePath, err)
    return "", err
  }
  defer file.Close()

  md5 := md5.New()
  if _, err := io.Copy(md5, file); err != nil {
    logrus.Debugf("could copy file at '%'", filePath, err)
    return "", err
  }

  var result []byte
  return string(md5.Sum(result)), nil
}

func getLcLocation() (string, error){
  // NOTE: if os.Args[0] is a symlink, this code will update the actual binary, not the link
  lcPath, err := osext.Executable()
  if err != nil {
    logrus.Debugf("lc not found", err)
    return "", err
  }
  return lcPath, nil
}

// move src file into a tmp file in the given 'dir'
// returns fullpath of new location
func mvLc(tmpDir string, src string) (string, error) {
  tmpLocation := fmt.Sprintf("%s/%s", tmpDir, "lc.old")
  logrus.Debugf("moving binary '%s' to '%s'", src, tmpLocation)
  if err := swap(src, tmpLocation); err != nil {
      return "", err
  }
  return tmpLocation, nil
}

// Will download the new binary from the given url into the given 'dir'
// returns full path of binary
func downloadNew(tmpDir string, url string) (string, error) {
  tmpLocation := fmt.Sprintf("%s/%s", tmpDir, "lc.new")
  logrus.Debugf("Downloading new binary using url: %q", url)
  if err := installNew(url, tmpLocation); err != nil {
    return "", err
  }
  return tmpLocation, nil
}

func installNew(url string, target string) error {
  tr := &http.Transport{
      TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  }
  client := &http.Client{Transport: tr}

  resp, err := client.Get(url)
  if err != nil {
    logrus.Debugf("failed downloading binary", err)
  	return err
  }
  if resp.StatusCode != 200 {
    return fmt.Errorf("failed downloading binary, invalid http response: %d", resp.StatusCode)
  }
  defer resp.Body.Close()

  out, err := os.Create(target)
  if err != nil {
    logrus.Debugf("failed creating new lc file", err)
    return err
  }
  defer out.Close()

  if n, err := io.Copy(out, resp.Body); err != nil {
    logrus.Debugf("failed copying new lc file", err)
    return err
  } else {
    logrus.Debugf("successfully coppied %d bytes", n)
  }

  if err := os.Chmod(target, os.FileMode(0755)); err != nil {
    logrus.Debugf("failed making lc executable", err)
    return err
  }
  return nil
}
<|endoftext|>


<commit_before>package commands

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/sys/windows"

	"github.com/zetamatta/go-windows-netresource"
	"github.com/zetamatta/go-windows-su"
	"github.com/zetamatta/go-windows-subst"

	"github.com/zetamatta/nyagos/shell"
)

var isWindowsTerminal = os.Getenv("WT_SESSION") != "" && os.Getenv("WT_PROFILE_ID") != ""

func _getwd() string {
	wd, err := os.Getwd()
	if err != nil {
		return ""
	}
	return wd
}

func _clone(action string, out io.Writer) (int, error) {
	wd := _getwd()
	var err error
	var me string
	me, err = os.Executable()
	if err != nil {
		return 1, err
	}
	var pid int
	if _me, err := filepath.EvalSymlinks(me); err == nil {
		me = _me
	}
	if isWindowsTerminal {
		pid, err = su.ShellExecute(action, "wt.exe",
			fmt.Sprintf(`--window 1 new-tab -- "%s" -k ""cd "%s"""`, me, wd), wd)
	} else {
		pid, err = su.ShellExecute(action, me, "", wd)
	}
	if err != nil {
		pid, err = su.ShellExecute(action, "CMD.EXE", "/c \""+me+"\"", wd)
		if err != nil {
			return 1, err // return original error
		}
	}
	if pid > 0 {
		fmt.Fprintf(out, "[%d]\n", pid)
		if process, err := os.FindProcess(pid); err == nil {
			go func() {
				process.Wait()
				shell.Message("[%d]+ Done\n", pid)
			}()
		}
	}
	return 0, nil
}

func cmdClone(ctx context.Context, cmd Param) (int, error) {
	return _clone("open", cmd.Err())
}

func runAsByWindowsTerminal(me, arguments string) (int, error) {
	// We can not launch wt.exe as admin-mode directly because of OS's bug.
	// (See also https://github.com/microsoft/terminal/issues/3145 )
	var param strings.Builder
	param.WriteString("/c wt.exe new-tab \"")
	param.WriteString(me)
	param.WriteString("\" ")
	param.WriteString(arguments)
	return su.Param{
		Action: "runas",
		Path:   "cmd.exe",
		Param:  param.String(),
		Show:   su.HIDE,
	}.ShellExecute()
}

func cmdSu(ctx context.Context, cmd Param) (int, error) {
	me, err := os.Executable()
	if err != nil {
		return 1, err
	}
	if me2, err2 := filepath.EvalSymlinks(me); err2 == nil {
		me = me2
	}
	wd, err := os.Getwd()
	if err != nil {
		return 2, err
	}
	if strings.HasSuffix(wd, `\`) {
		wd += "."
	}

	var buffer strings.Builder

	if netdrives, err := netresource.GetNetDrives(); err == nil {
		for _, n := range netdrives {
			fmt.Fprintf(&buffer, ` --netuse "%c:=%s"`, n.Letter, n.Remote)
		}
	}
	if drives, err := netresource.GetDrives(); err == nil {
		for _, d := range drives {
			if d.Type == windows.DRIVE_FIXED {
				mountPt := string([]byte{byte(d.Letter), ':'})
				target, err := subst.QueryRaw(mountPt)
				if err == nil && target[:4] == `\??\` {
					fmt.Fprintf(&buffer, ` --subst "%c:=%s"`, d.Letter, target[4:])
				}

			}
		}
	}
	fmt.Fprintf(&buffer, ` --chdir "%s"`, wd)

	var pid int

	if isWindowsTerminal {
		pid, err = runAsByWindowsTerminal(me, buffer.String())
	} else {
		pid, err = su.ShellExecute("runas", me, buffer.String(), "")
	}
	if err != nil {
		return 3, err
	}
	if pid > 0 {
		fmt.Fprintf(cmd.Err(), "[%d]\n", pid)
	}
	return 0, nil
}
<commit_msg>clone: wt.exe: window-number should be 0 (self-window)<commit_after>package commands

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"golang.org/x/sys/windows"

	"github.com/zetamatta/go-windows-netresource"
	"github.com/zetamatta/go-windows-su"
	"github.com/zetamatta/go-windows-subst"

	"github.com/zetamatta/nyagos/shell"
)

var isWindowsTerminal = os.Getenv("WT_SESSION") != "" && os.Getenv("WT_PROFILE_ID") != ""

func _getwd() string {
	wd, err := os.Getwd()
	if err != nil {
		return ""
	}
	return wd
}

func _clone(action string, out io.Writer) (int, error) {
	wd := _getwd()
	var err error
	var me string
	me, err = os.Executable()
	if err != nil {
		return 1, err
	}
	var pid int
	if _me, err := filepath.EvalSymlinks(me); err == nil {
		me = _me
	}
	if isWindowsTerminal {
		pid, err = su.ShellExecute(action, "wt.exe",
			fmt.Sprintf(`--window 0 new-tab -- "%s" -k ""cd "%s"""`, me, wd), wd)
	} else {
		pid, err = su.ShellExecute(action, me, "", wd)
	}
	if err != nil {
		pid, err = su.ShellExecute(action, "CMD.EXE", "/c \""+me+"\"", wd)
		if err != nil {
			return 1, err // return original error
		}
	}
	if pid > 0 {
		fmt.Fprintf(out, "[%d]\n", pid)
		if process, err := os.FindProcess(pid); err == nil {
			go func() {
				process.Wait()
				shell.Message("[%d]+ Done\n", pid)
			}()
		}
	}
	return 0, nil
}

func cmdClone(ctx context.Context, cmd Param) (int, error) {
	return _clone("open", cmd.Err())
}

func runAsByWindowsTerminal(me, arguments string) (int, error) {
	// We can not launch wt.exe as admin-mode directly because of OS's bug.
	// (See also https://github.com/microsoft/terminal/issues/3145 )
	var param strings.Builder
	param.WriteString("/c wt.exe new-tab \"")
	param.WriteString(me)
	param.WriteString("\" ")
	param.WriteString(arguments)
	return su.Param{
		Action: "runas",
		Path:   "cmd.exe",
		Param:  param.String(),
		Show:   su.HIDE,
	}.ShellExecute()
}

func cmdSu(ctx context.Context, cmd Param) (int, error) {
	me, err := os.Executable()
	if err != nil {
		return 1, err
	}
	if me2, err2 := filepath.EvalSymlinks(me); err2 == nil {
		me = me2
	}
	wd, err := os.Getwd()
	if err != nil {
		return 2, err
	}
	if strings.HasSuffix(wd, `\`) {
		wd += "."
	}

	var buffer strings.Builder

	if netdrives, err := netresource.GetNetDrives(); err == nil {
		for _, n := range netdrives {
			fmt.Fprintf(&buffer, ` --netuse "%c:=%s"`, n.Letter, n.Remote)
		}
	}
	if drives, err := netresource.GetDrives(); err == nil {
		for _, d := range drives {
			if d.Type == windows.DRIVE_FIXED {
				mountPt := string([]byte{byte(d.Letter), ':'})
				target, err := subst.QueryRaw(mountPt)
				if err == nil && target[:4] == `\??\` {
					fmt.Fprintf(&buffer, ` --subst "%c:=%s"`, d.Letter, target[4:])
				}

			}
		}
	}
	fmt.Fprintf(&buffer, ` --chdir "%s"`, wd)

	var pid int

	if isWindowsTerminal {
		pid, err = runAsByWindowsTerminal(me, buffer.String())
	} else {
		pid, err = su.ShellExecute("runas", me, buffer.String(), "")
	}
	if err != nil {
		return 3, err
	}
	if pid > 0 {
		fmt.Fprintf(cmd.Err(), "[%d]\n", pid)
	}
	return 0, nil
}
<|endoftext|>


<commit_before>package commands

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/github/git-lfs/lfs"
	"github.com/github/git-lfs/vendor/_nuts/github.com/spf13/cobra"
)

var (
	trackCmd = &cobra.Command{
		Use:   "track",
		Short: "Manipulate .gitattributes",
		Run:   trackCommand,
	}
)

func trackCommand(cmd *cobra.Command, args []string) {
	if lfs.LocalGitDir == "" {
		Print("Not a git repository.")
		os.Exit(128)
	}

	lfs.InstallHooks(false)
	knownPaths := findPaths()

	if len(args) == 0 {
		Print("Listing tracked paths")
		for _, t := range knownPaths {
			Print("    %s (%s)", t.Path, t.Source)
		}
		return
	}

	addTrailingLinebreak := needsTrailingLinebreak(".gitattributes")
	attributesFile, err := os.OpenFile(".gitattributes", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660)
	if err != nil {
		Print("Error opening .gitattributes file")
		return
	}
	defer attributesFile.Close()

	if addTrailingLinebreak {
		if _, err := attributesFile.WriteString("\n"); err != nil {
			Print("Error writing to .gitattributes")
		}
	}

ArgsLoop:
	for _, t := range args {
		for _, k := range knownPaths {
			if t == k.Path {
				Print("%s already supported", t)
				continue ArgsLoop
			}
		}

		encodedArg := strings.Replace(t, " ", "[[:space:]]", -1)
		_, err := attributesFile.WriteString(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -crlf\n", encodedArg))
		if err != nil {
			Print("Error adding path %s", t)
			continue
		}
		Print("Tracking %s", t)
	}
}

type mediaPath struct {
	Path   string
	Source string
}

func findPaths() []mediaPath {
	paths := make([]mediaPath, 0)
	wd, _ := os.Getwd()

	for _, path := range findAttributeFiles() {
		attributes, err := os.Open(path)
		if err != nil {
			continue
		}

		scanner := bufio.NewScanner(attributes)
		for scanner.Scan() {
			line := scanner.Text()
			if line == "" {
				continue
			}

			if strings.Contains(line, "filter=lfs") {
				fields := strings.Fields(line)
				relPath, _ := filepath.Rel(wd, path)
				paths = append(paths, mediaPath{Path: fields[0], Source: relPath})
			}
		}
	}

	return paths
}

func findAttributeFiles() []string {
	paths := make([]string, 0)

	repoAttributes := filepath.Join(lfs.LocalGitDir, "info", "attributes")
	if info, err := os.Stat(repoAttributes); err == nil && !info.IsDir() {
		paths = append(paths, repoAttributes)
	}

	filepath.Walk(lfs.LocalWorkingDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !info.IsDir() && (filepath.Base(path) == ".gitattributes") {
			paths = append(paths, path)
		}
		return nil
	})

	return paths
}

func needsTrailingLinebreak(filename string) bool {
	file, err := os.Open(filename)
	if err != nil {
		return false
	}
	defer file.Close()

	buf := make([]byte, 16384)
	bytesRead := 0
	for {
		n, err := file.Read(buf)
		if err == io.EOF {
			break
		} else if err != nil {
			return false
		}
		bytesRead = n
	}

	return !strings.HasSuffix(string(buf[0:bytesRead]), "\n")
}

func init() {
	RootCmd.AddCommand(trackCmd)
}
<commit_msg>Removed unnecessary if statement<commit_after>package commands

import (
	"bufio"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/github/git-lfs/lfs"
	"github.com/github/git-lfs/vendor/_nuts/github.com/spf13/cobra"
)

var (
	trackCmd = &cobra.Command{
		Use:   "track",
		Short: "Manipulate .gitattributes",
		Run:   trackCommand,
	}
)

func trackCommand(cmd *cobra.Command, args []string) {
	if lfs.LocalGitDir == "" {
		Print("Not a git repository.")
		os.Exit(128)
	}

	lfs.InstallHooks(false)
	knownPaths := findPaths()

	if len(args) == 0 {
		Print("Listing tracked paths")
		for _, t := range knownPaths {
			Print("    %s (%s)", t.Path, t.Source)
		}
		return
	}

	addTrailingLinebreak := needsTrailingLinebreak(".gitattributes")
	attributesFile, err := os.OpenFile(".gitattributes", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660)
	if err != nil {
		Print("Error opening .gitattributes file")
		return
	}
	defer attributesFile.Close()

	if addTrailingLinebreak {
		if _, err := attributesFile.WriteString("\n"); err != nil {
			Print("Error writing to .gitattributes")
		}
	}

ArgsLoop:
	for _, t := range args {
		for _, k := range knownPaths {
			if t == k.Path {
				Print("%s already supported", t)
				continue ArgsLoop
			}
		}

		encodedArg := strings.Replace(t, " ", "[[:space:]]", -1)
		_, err := attributesFile.WriteString(fmt.Sprintf("%s filter=lfs diff=lfs merge=lfs -crlf\n", encodedArg))
		if err != nil {
			Print("Error adding path %s", t)
			continue
		}
		Print("Tracking %s", t)
	}
}

type mediaPath struct {
	Path   string
	Source string
}

func findPaths() []mediaPath {
	paths := make([]mediaPath, 0)
	wd, _ := os.Getwd()

	for _, path := range findAttributeFiles() {
		attributes, err := os.Open(path)
		if err != nil {
			continue
		}

		scanner := bufio.NewScanner(attributes)
		for scanner.Scan() {
			line := scanner.Text()
			if strings.Contains(line, "filter=lfs") {
				fields := strings.Fields(line)
				relPath, _ := filepath.Rel(wd, path)
				paths = append(paths, mediaPath{Path: fields[0], Source: relPath})
			}
		}
	}

	return paths
}

func findAttributeFiles() []string {
	paths := make([]string, 0)

	repoAttributes := filepath.Join(lfs.LocalGitDir, "info", "attributes")
	if info, err := os.Stat(repoAttributes); err == nil && !info.IsDir() {
		paths = append(paths, repoAttributes)
	}

	filepath.Walk(lfs.LocalWorkingDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !info.IsDir() && (filepath.Base(path) == ".gitattributes") {
			paths = append(paths, path)
		}
		return nil
	})

	return paths
}

func needsTrailingLinebreak(filename string) bool {
	file, err := os.Open(filename)
	if err != nil {
		return false
	}
	defer file.Close()

	buf := make([]byte, 16384)
	bytesRead := 0
	for {
		n, err := file.Read(buf)
		if err == io.EOF {
			break
		} else if err != nil {
			return false
		}
		bytesRead = n
	}

	return !strings.HasSuffix(string(buf[0:bytesRead]), "\n")
}

func init() {
	RootCmd.AddCommand(trackCmd)
}
<|endoftext|>


<commit_before>package pubsub

import "testing"

type str struct {
	Val string
}

func TestSub(t *testing.T) {
	if Sub(Location, func(teststr *str) {}) != nil {
		t.Fail()
	}

	// Make sure we get an error if we don't pass a func
	if Sub(Location, "String") == nil {
		t.Fail()
	}
}

func TestPublish(t *testing.T) {
	msgs := make(chan string)
	Sub(Location, func(s string) {
		msgs <- s
	})
	Pub(Location, "test")

	msg := <-msgs
	if msg != "test" {
		t.Error("Did not get expected string!")
	}
}
<commit_msg>Errcheck'ed github.com/getlantern/flashlight/pubsub<commit_after>package pubsub

import "testing"

type str struct {
	Val string
}

func TestSub(t *testing.T) {
	if Sub(Location, func(teststr *str) {}) != nil {
		t.Fail()
	}

	// Make sure we get an error if we don't pass a func
	if Sub(Location, "String") == nil {
		t.Fail()
	}
}

func TestPublish(t *testing.T) {
	msgs := make(chan string)
	err := Sub(Location, func(s string) {
		msgs <- s
	})
	if err != nil {
		t.Errorf("Error subscribing to location %v: %v", Location, err)
	}

	msg := <-msgs
	if msg != "test" {
		t.Error("Did not get expected string!")
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2018 Google 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 endpoints

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"

	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"cloud.google.com/go/pubsub"
	"github.com/google/splice/appengine/server"
	basic "github.com/google/splice/appengine/validators"
	"github.com/google/splice/models"
)

// AttendedRequestHandler implements http.Handler for user interactive joins.
type AttendedRequestHandler struct{}

func (ah AttendedRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	checks, err := validatorsNewAttended()
	if err != nil {
		log.Errorf(ctx, "Validator setup returned %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	requestResponse(ctx, w, r, checks)
}

// UnattendedRequestHandler implements http.Handler for unattended joins.
type UnattendedRequestHandler struct{}

func (uh UnattendedRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	checks, err := validatorsNewUnattended()
	if err != nil {
		log.Errorf(ctx, "Validator setup returned %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	requestResponse(ctx, w, r, checks)
}

// requestResponse performs necessary cleanup and provides a response to the client.
func requestResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, checks []basic.Validator) {
	resp := ProcessRequest(ctx, w, r, checks)
	if resp.ErrorCode != server.StatusSuccess {
		log.Warningf(ctx, "could not process request %v", resp)
	}

	jsonResponse, err := json.Marshal(resp)
	if err != nil {
		log.Errorf(ctx, "json.Marshal(%v) failed: %v", resp, err)
		http.Error(
			w,
			fmt.Sprintf("json.Marshal(%v) failed: %v", resp, err),
			http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(int(server.StatusSuccess))
	w.Write(jsonResponse)

	if resp.ErrorCode == server.StatusSuccess {
		log.Infof(ctx, "successfully processed requestID '%q'", resp.RequestID)
	}
}

// ProcessRequest takes a models.Request that is provided by the client,
// and validates it. A response is provided using models.Response.
func ProcessRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, checks []basic.Validator) models.Response {
	request, code, err := unmarshalRequest(r)
	if err != nil {
		return models.Response{
			ErrorCode: code,
			Status:    err.Error(),
		}
	}

	if err := verifyCert(ctx, request.ClientID, r); err != nil {
		return models.Response{
			ErrorCode: server.StatusInvalidCertError,
			Status:    err.Error(),
		}
	}

	// Run this request through all validators
	for _, c := range checks {
		status, err := c.Check(ctx, &request)
		if err != nil {
			return models.Response{
				ErrorCode: status,
				Status:    err.Error(),
			}
		}
	}

	// New requests require a cryptographically secure requestID
	// of a sufficient length. Existing requests re-use their
	// existing requestID.
	//
	request.RequestID, err = generateReqID(reqIDLen)
	if err != nil {
		return models.Response{
			ErrorCode: server.StatusReqProcessingError,
			Status:    fmt.Sprintf("generateReqID(%d) returned %v", reqIDLen, err),
		}
	}

	request.AcceptTime = time.Now()
	request.Status = models.RequestStatusAccepted

	// Initialize an empty datastore client at the appropriate scope.
	dc := &Client{Req: &models.Request{}}
	log.Infof(ctx, "AttemptReuse returned %t in the App Engine request", request.AttemptReuse)
	if useDatastore {
		var status server.StatusCode
		dc, status, err = NewClient(ctx, &request)
		if err != nil {
			return models.Response{
				ErrorCode: status,
				Status:    err.Error(),
			}
		}
		defer dc.Close()

		if err = dc.StartTx(ctx); err != nil {
			return models.Response{
				ErrorCode: server.StatusDatastoreTxCreateError,
				Status:    err.Error(),
			}
		}
		defer dc.RollbackTx()

		if status, err := dc.Save(ctx); err != nil {
			return models.Response{
				ErrorCode: status,
				Status:    err.Error(),
			}
		}

		if err := dc.CommitTx(); err != nil {
			return models.Response{
				ErrorCode: server.StatusDatastoreTxCommitError,
				Status:    err.Error(),
			}
		}

		// Failure to cleanup orphans should be reported
		// but should not stop processing.
		if err := cleanupOrphans(ctx, dc); err != nil {
			log.Warningf(ctx, "cleanupOrphans failed with %v ", err)
		}
	}

	if usePubsub {
		if err := publishRequest(ctx, request.RequestID); err != nil {
			return models.Response{
				ErrorCode: server.StatusPubsubFailure,
				Status:    err.Error(),
			}
		}
	}

	return models.Response{
		Status:    models.RequestStatusAccepted,
		RequestID: request.RequestID,
		ErrorCode: server.StatusSuccess,
	}
}

// GenerateReqID returns a URL-safe, base64 encoded
// securely generated random requestID to identify a request.
// It takes as input the length in bytes of the token to be
// generated. E.G. 64 bytes (512 bit), or 128 bytes (1024 bit).
func generateReqID(n uint) (string, error) {
	if n <= 0 {
		return "", fmt.Errorf("invalid length %d requested", n)
	}
	rand.Seed(time.Now().UnixNano())
	b := make([]byte, n)
	_, err := rand.Read(b)
	if err != nil {
		return "", fmt.Errorf("rand.Read returned %v", err)
	}
	return base64.URLEncoding.EncodeToString(b), nil
}

// unmarshalRequest takes a raw inbound request and returns
// a models.Request for processing
func unmarshalRequest(r *http.Request) (models.Request, server.StatusCode, error) {
	var clientRequest models.ClientRequest

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return models.Request{},
			server.StatusRequestUnreadable,
			errors.New("unable to read HTTP request body")
	}

	if len(body) == 0 {
		return models.Request{},
			server.StatusJSONEmpty,
			errors.New("empty HTTP JSON request body")
	}

	if err = json.Unmarshal(body, &clientRequest); err != nil {
		return models.Request{},
			server.StatusJSONUmarshalError,
			errors.New("unable to unmarshal JSON request")
	}

	return models.Request{
			Hostname:    clientRequest.Hostname,
			ClientID:    clientRequest.ClientID,
			ClientCert:  clientRequest.ClientCert,
			GCEMetadata: clientRequest.GCEMetadata,
		},
		server.StatusSuccess,
		nil
}

// cleanupOrphans looks for requests that are too old or were
// tried once and not re-tried and makes sure that they are not
// left in an indeterminate state.
func cleanupOrphans(ctx context.Context, dc *Client) error {
	// Orphans older than 24 hours are cleaned up.
	olderThan := 24 * time.Hour

	// Check the following statuses for orphaned entries.
	s := []string{
		models.RequestStatusAccepted,
		models.RequestStatusProcessing,
		models.RequestStatusCompleted,
	}

	if err := dc.StartTx(ctx); err != nil {
		return fmt.Errorf("dc.StartTx returned %v", err)
	}
	defer dc.RollbackTx()

	// Cleanup requests by type because the datastore GetAll
	// func contcatenates filters using AND.
	for _, kind := range s {
		keys, requests, err := dc.FindOrphans(ctx, olderThan, kind)
		if err != nil {
			return fmt.Errorf("FindOrphans(%d, %s) = %v", olderThan, kind, err)
		}

		if len(requests) > 0 {
			for i, orphan := range requests {
				orphan.Status = models.RequestStatusFailed
				dc.Req = nil
				dc.Req = &orphan
				dc.Keys[0] = keys[i]
				if _, err = dc.Save(ctx); err != nil {
					return err
				}
				log.Infof(ctx, "cleaned up orphan with reqID = %q ", orphan.RequestID)
			}
		}
	}

	if err := dc.CommitTx(); err != nil {
		return fmt.Errorf("dc.CommitTx returned %v", err)
	}

	return nil
}

// Publishes a request to the pubsub channel.
func publishRequest(ctx context.Context, reqID string) error {
	envProject := appengine.AppID(ctx)
	envTopic := os.Getenv("PUBSUB_TOPIC")
	if envTopic == "" {
		return errors.New("PUBSUB_TOPIC environment variable not set")
	}

	ps, err := pubsub.NewClient(ctx, envProject)
	if err != nil {
		return fmt.Errorf("pubsub.NewClient(%q) returned: %v", envProject, err)
	}

	// Create topic if it doesn't exist.
	topic, err := ps.CreateTopic(ctx, envTopic)
	if err != nil && !strings.Contains(err.Error(), "AlreadyExists") {
		return fmt.Errorf("failed to create topic %q: %v", envTopic, err)
	}
	defer topic.Stop()
	res := topic.Publish(ctx, &pubsub.Message{Data: []byte(reqID)})

	msgID, err := res.Get(ctx)
	if err != nil {
		return fmt.Errorf("topic.Publish error publishing to topic %s: %v, %v", envTopic, res, err)
	}

	log.Infof(ctx, "request id %q published with msg id %q", reqID, msgID)
	return nil
}
<commit_msg>CreateTopic doesn't return the topic if it already exists<commit_after>/*
Copyright 2018 Google 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 endpoints

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"math/rand"
	"net/http"
	"os"
	"strings"
	"time"

	"google.golang.org/appengine"
	"google.golang.org/appengine/log"
	"cloud.google.com/go/pubsub"
	"github.com/google/splice/appengine/server"
	basic "github.com/google/splice/appengine/validators"
	"github.com/google/splice/models"
)

// AttendedRequestHandler implements http.Handler for user interactive joins.
type AttendedRequestHandler struct{}

func (ah AttendedRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	checks, err := validatorsNewAttended()
	if err != nil {
		log.Errorf(ctx, "Validator setup returned %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	requestResponse(ctx, w, r, checks)
}

// UnattendedRequestHandler implements http.Handler for unattended joins.
type UnattendedRequestHandler struct{}

func (uh UnattendedRequestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := appengine.NewContext(r)
	checks, err := validatorsNewUnattended()
	if err != nil {
		log.Errorf(ctx, "Validator setup returned %v", err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	requestResponse(ctx, w, r, checks)
}

// requestResponse performs necessary cleanup and provides a response to the client.
func requestResponse(ctx context.Context, w http.ResponseWriter, r *http.Request, checks []basic.Validator) {
	resp := ProcessRequest(ctx, w, r, checks)
	if resp.ErrorCode != server.StatusSuccess {
		log.Warningf(ctx, "could not process request %v", resp)
	}

	jsonResponse, err := json.Marshal(resp)
	if err != nil {
		log.Errorf(ctx, "json.Marshal(%v) failed: %v", resp, err)
		http.Error(
			w,
			fmt.Sprintf("json.Marshal(%v) failed: %v", resp, err),
			http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(int(server.StatusSuccess))
	w.Write(jsonResponse)

	if resp.ErrorCode == server.StatusSuccess {
		log.Infof(ctx, "successfully processed requestID '%q'", resp.RequestID)
	}
}

// ProcessRequest takes a models.Request that is provided by the client,
// and validates it. A response is provided using models.Response.
func ProcessRequest(ctx context.Context, w http.ResponseWriter, r *http.Request, checks []basic.Validator) models.Response {
	request, code, err := unmarshalRequest(r)
	if err != nil {
		return models.Response{
			ErrorCode: code,
			Status:    err.Error(),
		}
	}

	if err := verifyCert(ctx, request.ClientID, r); err != nil {
		return models.Response{
			ErrorCode: server.StatusInvalidCertError,
			Status:    err.Error(),
		}
	}

	// Run this request through all validators
	for _, c := range checks {
		status, err := c.Check(ctx, &request)
		if err != nil {
			return models.Response{
				ErrorCode: status,
				Status:    err.Error(),
			}
		}
	}

	// New requests require a cryptographically secure requestID
	// of a sufficient length. Existing requests re-use their
	// existing requestID.
	//
	request.RequestID, err = generateReqID(reqIDLen)
	if err != nil {
		return models.Response{
			ErrorCode: server.StatusReqProcessingError,
			Status:    fmt.Sprintf("generateReqID(%d) returned %v", reqIDLen, err),
		}
	}

	request.AcceptTime = time.Now()
	request.Status = models.RequestStatusAccepted

	// Initialize an empty datastore client at the appropriate scope.
	dc := &Client{Req: &models.Request{}}
	log.Infof(ctx, "AttemptReuse returned %t in the App Engine request", request.AttemptReuse)
	if useDatastore {
		var status server.StatusCode
		dc, status, err = NewClient(ctx, &request)
		if err != nil {
			return models.Response{
				ErrorCode: status,
				Status:    err.Error(),
			}
		}
		defer dc.Close()

		if err = dc.StartTx(ctx); err != nil {
			return models.Response{
				ErrorCode: server.StatusDatastoreTxCreateError,
				Status:    err.Error(),
			}
		}
		defer dc.RollbackTx()

		if status, err := dc.Save(ctx); err != nil {
			return models.Response{
				ErrorCode: status,
				Status:    err.Error(),
			}
		}

		if err := dc.CommitTx(); err != nil {
			return models.Response{
				ErrorCode: server.StatusDatastoreTxCommitError,
				Status:    err.Error(),
			}
		}

		// Failure to cleanup orphans should be reported
		// but should not stop processing.
		if err := cleanupOrphans(ctx, dc); err != nil {
			log.Warningf(ctx, "cleanupOrphans failed with %v ", err)
		}
	}

	if usePubsub {
		if err := publishRequest(ctx, request.RequestID); err != nil {
			return models.Response{
				ErrorCode: server.StatusPubsubFailure,
				Status:    err.Error(),
			}
		}
	}

	return models.Response{
		Status:    models.RequestStatusAccepted,
		RequestID: request.RequestID,
		ErrorCode: server.StatusSuccess,
	}
}

// GenerateReqID returns a URL-safe, base64 encoded
// securely generated random requestID to identify a request.
// It takes as input the length in bytes of the token to be
// generated. E.G. 64 bytes (512 bit), or 128 bytes (1024 bit).
func generateReqID(n uint) (string, error) {
	if n <= 0 {
		return "", fmt.Errorf("invalid length %d requested", n)
	}
	rand.Seed(time.Now().UnixNano())
	b := make([]byte, n)
	_, err := rand.Read(b)
	if err != nil {
		return "", fmt.Errorf("rand.Read returned %v", err)
	}
	return base64.URLEncoding.EncodeToString(b), nil
}

// unmarshalRequest takes a raw inbound request and returns
// a models.Request for processing
func unmarshalRequest(r *http.Request) (models.Request, server.StatusCode, error) {
	var clientRequest models.ClientRequest

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return models.Request{},
			server.StatusRequestUnreadable,
			errors.New("unable to read HTTP request body")
	}

	if len(body) == 0 {
		return models.Request{},
			server.StatusJSONEmpty,
			errors.New("empty HTTP JSON request body")
	}

	if err = json.Unmarshal(body, &clientRequest); err != nil {
		return models.Request{},
			server.StatusJSONUmarshalError,
			errors.New("unable to unmarshal JSON request")
	}

	return models.Request{
			Hostname:    clientRequest.Hostname,
			ClientID:    clientRequest.ClientID,
			ClientCert:  clientRequest.ClientCert,
			GCEMetadata: clientRequest.GCEMetadata,
		},
		server.StatusSuccess,
		nil
}

// cleanupOrphans looks for requests that are too old or were
// tried once and not re-tried and makes sure that they are not
// left in an indeterminate state.
func cleanupOrphans(ctx context.Context, dc *Client) error {
	// Orphans older than 24 hours are cleaned up.
	olderThan := 24 * time.Hour

	// Check the following statuses for orphaned entries.
	s := []string{
		models.RequestStatusAccepted,
		models.RequestStatusProcessing,
		models.RequestStatusCompleted,
	}

	if err := dc.StartTx(ctx); err != nil {
		return fmt.Errorf("dc.StartTx returned %v", err)
	}
	defer dc.RollbackTx()

	// Cleanup requests by type because the datastore GetAll
	// func contcatenates filters using AND.
	for _, kind := range s {
		keys, requests, err := dc.FindOrphans(ctx, olderThan, kind)
		if err != nil {
			return fmt.Errorf("FindOrphans(%d, %s) = %v", olderThan, kind, err)
		}

		if len(requests) > 0 {
			for i, orphan := range requests {
				orphan.Status = models.RequestStatusFailed
				dc.Req = nil
				dc.Req = &orphan
				dc.Keys[0] = keys[i]
				if _, err = dc.Save(ctx); err != nil {
					return err
				}
				log.Infof(ctx, "cleaned up orphan with reqID = %q ", orphan.RequestID)
			}
		}
	}

	if err := dc.CommitTx(); err != nil {
		return fmt.Errorf("dc.CommitTx returned %v", err)
	}

	return nil
}

// Publishes a request to the pubsub channel.
func publishRequest(ctx context.Context, reqID string) error {
	envProject := appengine.AppID(ctx)
	envTopic := os.Getenv("PUBSUB_TOPIC")
	if envTopic == "" {
		return errors.New("PUBSUB_TOPIC environment variable not set")
	}

	ps, err := pubsub.NewClient(ctx, envProject)
	if err != nil {
		return fmt.Errorf("pubsub.NewClient(%q) returned: %v", envProject, err)
	}

	// Create topic if it doesn't exist.
	_, err = ps.CreateTopic(ctx, envTopic)
	if err != nil && !strings.Contains(err.Error(), "AlreadyExists") {
		return fmt.Errorf("failed to create topic %q: %v", envTopic, err)
	}

	// CreateTopic doesn't return the topic if it already exists
	topic := ps.Topic(envTopic)
	defer topic.Stop()
	res := topic.Publish(ctx, &pubsub.Message{Data: []byte(reqID)})

	msgID, err := res.Get(ctx)
	if err != nil {
		return fmt.Errorf("topic.Publish error publishing to topic %s: %v, %v", envTopic, res, err)
	}

	log.Infof(ctx, "request id %q published with msg id %q", reqID, msgID)
	return nil
}
<|endoftext|>