<commit_before>package handler

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/Lunchr/luncher-api/db"
	"github.com/Lunchr/luncher-api/db/model"
	"github.com/Lunchr/luncher-api/router"
	"github.com/Lunchr/luncher-api/session"
	"github.com/Lunchr/luncher-api/storage"
	"github.com/deiwin/facebook"
	"github.com/julienschmidt/httprouter"
	"gopkg.in/mgo.v2/bson"
)

type HandlerWithUserAndOffer func(w http.ResponseWriter, r *http.Request, user *model.User, offer *model.Offer) *router.HandlerError

// PostOffers handles POST requests to /offers. It stores the offer in the DB and
// sends it to Facebook to be posted on the page's wall at the requested time.
func PostOffers(offersCollection db.Offers, usersCollection db.Users, restaurantsCollection db.Restaurants,
	sessionManager session.Manager, fbAuth facebook.Authenticator, imageStorage storage.Images) router.Handler {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User) *router.HandlerError {
		restaurant, err := restaurantsCollection.GetID(user.RestaurantIDs[0])
		if err != nil {
			return router.NewHandlerError(err, "Couldn't find a restaurant related to this user", http.StatusInternalServerError)
		}
		var api facebook.API
		if restaurant.FacebookPageID != "" {
			api = fbAuth.APIConnection(&user.Session.FacebookUserToken)
		}
		offerPOST, err := parseOffer(r, restaurant)
		if err != nil {
			return router.NewHandlerError(err, "Failed to parse the offer", http.StatusBadRequest)
		}
		offer, err := model.MapOfferPOSTToOffer(offerPOST, getImageDataToChecksumMapper(imageStorage))
		if err != nil {
			return router.NewHandlerError(err, "Failed to map the offer to the internal representation", http.StatusInternalServerError)
		}
		if restaurant.FacebookPageID != "" {
			message := formFBOfferMessage(*offer)
			post, err := api.PagePublish(user.Session.FacebookPageToken, restaurant.FacebookPageID, message)
			if err != nil {
				return router.NewHandlerError(err, "Failed to post the offer to Facebook", http.StatusBadGateway)
			}
			offer.FBPostID = post.ID
		}
		offers, err := offersCollection.Insert(offer)
		if err != nil {
			return router.NewHandlerError(err, "Failed to store the offer in the DB", http.StatusInternalServerError)
		}

		offerJSON, handlerError := mapOfferToJSON(offers[0], imageStorage)
		if handlerError != nil {
			return handlerError
		}
		return writeJSON(w, offerJSON)
	}
	return checkLogin(sessionManager, usersCollection, handler)
}

// PutOffers handles PUT requests to /offers. It updates the offer in the DB and
// updates the related Facebook post.
func PutOffers(offersCollection db.Offers, usersCollection db.Users, restaurantsCollection db.Restaurants,
	sessionManager session.Manager, fbAuth facebook.Authenticator, imageStorage storage.Images) router.HandlerWithParams {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, currentOffer *model.Offer) *router.HandlerError {
		restaurant, err := restaurantsCollection.GetID(user.RestaurantIDs[0])
		if err != nil {
			return router.NewHandlerError(err, "Couldn't find the restaurant this offer belongs to", http.StatusInternalServerError)
		}
		var api facebook.API
		if restaurant.FacebookPageID != "" {
			api = fbAuth.APIConnection(&user.Session.FacebookUserToken)
		}
		offerPOST, err := parseOffer(r, restaurant)
		if err != nil {
			return router.NewHandlerError(err, "Failed to parse the offer", http.StatusBadRequest)
		}
		// If the image_data field isn't set, the image field of offer also doesn't get set and
		// therefore the update won't affect the stored image. This in turn means, that currently
		// there's no way to update the offer to remove an image.
		offer, err := model.MapOfferPOSTToOffer(offerPOST, getImageDataToChecksumMapper(imageStorage))
		if err != nil {
			return router.NewHandlerError(err, "Failed to map the offer to the internal representation", http.StatusInternalServerError)
		}
		if restaurant.FacebookPageID != "" {
			if currentOffer.FBPostID != "" {
				err = api.PostDelete(user.Session.FacebookPageToken, currentOffer.FBPostID)
				if err != nil {
					return router.NewHandlerError(err, "Failed to delete the current post from Facebook", http.StatusBadGateway)
				}
			}
			message := formFBOfferMessage(*offer)
			post, err := api.PagePublish(user.Session.FacebookPageToken, restaurant.FacebookPageID, message)
			if err != nil {
				return router.NewHandlerError(err, "Failed to post the offer to Facebook", http.StatusBadGateway)
			}
			offer.FBPostID = post.ID
		}
		err = offersCollection.UpdateID(currentOffer.ID, offer)
		if err != nil {
			return router.NewHandlerError(err, "Failed to update the offer in DB", http.StatusInternalServerError)
		}
		offer.ID = currentOffer.ID

		offerJSON, handlerError := mapOfferToJSON(offer, imageStorage)
		if handlerError != nil {
			return handlerError
		}
		return writeJSON(w, offerJSON)
	}

	return checkLoginWithParams(sessionManager, usersCollection, forOffer(offersCollection, handler))
}

// DeleteOffers handles DELETE requests to /offers. It deletes the offer from the DB and
// deletes the related Facebook post.
func DeleteOffers(offersCollection db.Offers, usersCollection db.Users, sessionManager session.Manager, fbAuth facebook.Authenticator) router.HandlerWithParams {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, currentOffer *model.Offer) *router.HandlerError {
		fbAPI := fbAuth.APIConnection(&user.Session.FacebookUserToken)
		if currentOffer.FBPostID != "" {
			err := fbAPI.PostDelete(user.Session.FacebookPageToken, currentOffer.FBPostID)
			if err != nil {
				return router.NewHandlerError(err, "Failed to delete the current post from Facebook", http.StatusBadGateway)
			}
		}
		err := offersCollection.RemoveID(currentOffer.ID)
		if err != nil {
			return router.NewHandlerError(err, "Failed to delete the offer from DB", http.StatusInternalServerError)
		}
		w.WriteHeader(http.StatusOK)
		return nil
	}
	return checkLoginWithParams(sessionManager, usersCollection, forOffer(offersCollection, handler))
}

func forOffer(offersCollection db.Offers, handler HandlerWithUserAndOffer) HandlerWithParamsWithUser {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, user *model.User) *router.HandlerError {
		idString := ps.ByName("id")
		if !bson.IsObjectIdHex(idString) {
			return router.NewStringHandlerError("invalid offer ID", "", http.StatusBadRequest)
		}
		id := bson.ObjectIdHex(idString)
		offer, err := offersCollection.GetID(id)
		if err != nil {
			return router.NewHandlerError(err, "Couldn't find an offer with this ID", http.StatusNotFound)
		}
		return handler(w, r, user, offer)
	}
}

func formFBOfferMessage(o model.Offer) string {
	ingredients := strings.Join(o.Ingredients, ", ")
	capitalizedIngredients := capitalizeString(ingredients)
	return fmt.Sprintf("%s - %s", o.Title, capitalizedIngredients)
}

func capitalizeString(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToUpper(r)) + s[n:]
}

func parseOffer(r *http.Request, restaurant *model.Restaurant) (*model.OfferPOST, error) {
	var offer model.OfferPOST
	err := json.NewDecoder(r.Body).Decode(&offer)
	if err != nil {
		return nil, err
	}
	offer.Restaurant = model.OfferRestaurant{
		Name:     restaurant.Name,
		Region:   restaurant.Region,
		Address:  restaurant.Address,
		Location: restaurant.Location,
		Phone:    restaurant.Phone,
	}
	return &offer, nil
}

func getImageDataToChecksumMapper(imageStorage storage.Images) func(string) (string, error) {
	return func(imageData string) (string, error) {
		if imageData == "" {
			return "", nil
		}
		return parseAndStoreImage(imageData, imageStorage)
	}
}

func parseAndStoreImage(imageDataURL string, imageStorage storage.Images) (string, error) {
	imageChecksum, err := imageStorage.ChecksumDataURL(imageDataURL)
	if err != nil {
		return "", err
	}
	if err = imageStorage.StoreDataURL(imageDataURL); err != nil {
		return "", err
	}
	return imageChecksum, nil
}
<commit_msg>Refactor posting to FB into a separate function<commit_after>package handler

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/Lunchr/luncher-api/db"
	"github.com/Lunchr/luncher-api/db/model"
	"github.com/Lunchr/luncher-api/router"
	"github.com/Lunchr/luncher-api/session"
	"github.com/Lunchr/luncher-api/storage"
	"github.com/deiwin/facebook"
	"github.com/julienschmidt/httprouter"
	"gopkg.in/mgo.v2/bson"
)

type HandlerWithUserAndOffer func(w http.ResponseWriter, r *http.Request, user *model.User, offer *model.Offer) *router.HandlerError

// PostOffers handles POST requests to /offers. It stores the offer in the DB and
// sends it to Facebook to be posted on the page's wall at the requested time.
func PostOffers(offersCollection db.Offers, usersCollection db.Users, restaurantsCollection db.Restaurants,
	sessionManager session.Manager, fbAuth facebook.Authenticator, imageStorage storage.Images) router.Handler {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User) *router.HandlerError {
		restaurant, err := restaurantsCollection.GetID(user.RestaurantIDs[0])
		if err != nil {
			return router.NewHandlerError(err, "Couldn't find a restaurant related to this user", http.StatusInternalServerError)
		}
		var api facebook.API
		if restaurant.FacebookPageID != "" {
			api = fbAuth.APIConnection(&user.Session.FacebookUserToken)
		}
		offerPOST, err := parseOffer(r, restaurant)
		if err != nil {
			return router.NewHandlerError(err, "Failed to parse the offer", http.StatusBadRequest)
		}
		offer, err := model.MapOfferPOSTToOffer(offerPOST, getImageDataToChecksumMapper(imageStorage))
		if err != nil {
			return router.NewHandlerError(err, "Failed to map the offer to the internal representation", http.StatusInternalServerError)
		}
		fbPostID, handlerErr := postOfferToFB(*offer, user, restaurant, api)
		if handlerErr != nil {
			return handlerErr
		}
		offer.FBPostID = fbPostID

		offers, err := offersCollection.Insert(offer)
		if err != nil {
			return router.NewHandlerError(err, "Failed to store the offer in the DB", http.StatusInternalServerError)
		}

		offerJSON, handlerError := mapOfferToJSON(offers[0], imageStorage)
		if handlerError != nil {
			return handlerError
		}
		return writeJSON(w, offerJSON)
	}
	return checkLogin(sessionManager, usersCollection, handler)
}

// PutOffers handles PUT requests to /offers. It updates the offer in the DB and
// updates the related Facebook post.
func PutOffers(offersCollection db.Offers, usersCollection db.Users, restaurantsCollection db.Restaurants,
	sessionManager session.Manager, fbAuth facebook.Authenticator, imageStorage storage.Images) router.HandlerWithParams {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, currentOffer *model.Offer) *router.HandlerError {
		restaurant, err := restaurantsCollection.GetID(user.RestaurantIDs[0])
		if err != nil {
			return router.NewHandlerError(err, "Couldn't find the restaurant this offer belongs to", http.StatusInternalServerError)
		}
		var api facebook.API
		if restaurant.FacebookPageID != "" {
			api = fbAuth.APIConnection(&user.Session.FacebookUserToken)
		}
		offerPOST, err := parseOffer(r, restaurant)
		if err != nil {
			return router.NewHandlerError(err, "Failed to parse the offer", http.StatusBadRequest)
		}
		// If the image_data field isn't set, the image field of offer also doesn't get set and
		// therefore the update won't affect the stored image. This in turn means, that currently
		// there's no way to update the offer to remove an image.
		offer, err := model.MapOfferPOSTToOffer(offerPOST, getImageDataToChecksumMapper(imageStorage))
		if err != nil {
			return router.NewHandlerError(err, "Failed to map the offer to the internal representation", http.StatusInternalServerError)
		}
		if restaurant.FacebookPageID != "" {
			if currentOffer.FBPostID != "" {
				err = api.PostDelete(user.Session.FacebookPageToken, currentOffer.FBPostID)
				if err != nil {
					return router.NewHandlerError(err, "Failed to delete the current post from Facebook", http.StatusBadGateway)
				}
			}
			fbPostID, handlerErr := postOfferToFB(*offer, user, restaurant, api)
			if handlerErr != nil {
				return handlerErr
			}
			offer.FBPostID = fbPostID
		}
		err = offersCollection.UpdateID(currentOffer.ID, offer)
		if err != nil {
			return router.NewHandlerError(err, "Failed to update the offer in DB", http.StatusInternalServerError)
		}
		offer.ID = currentOffer.ID

		offerJSON, handlerError := mapOfferToJSON(offer, imageStorage)
		if handlerError != nil {
			return handlerError
		}
		return writeJSON(w, offerJSON)
	}

	return checkLoginWithParams(sessionManager, usersCollection, forOffer(offersCollection, handler))
}

// DeleteOffers handles DELETE requests to /offers. It deletes the offer from the DB and
// deletes the related Facebook post.
func DeleteOffers(offersCollection db.Offers, usersCollection db.Users, sessionManager session.Manager, fbAuth facebook.Authenticator) router.HandlerWithParams {
	handler := func(w http.ResponseWriter, r *http.Request, user *model.User, currentOffer *model.Offer) *router.HandlerError {
		fbAPI := fbAuth.APIConnection(&user.Session.FacebookUserToken)
		if currentOffer.FBPostID != "" {
			err := fbAPI.PostDelete(user.Session.FacebookPageToken, currentOffer.FBPostID)
			if err != nil {
				return router.NewHandlerError(err, "Failed to delete the current post from Facebook", http.StatusBadGateway)
			}
		}
		err := offersCollection.RemoveID(currentOffer.ID)
		if err != nil {
			return router.NewHandlerError(err, "Failed to delete the offer from DB", http.StatusInternalServerError)
		}
		w.WriteHeader(http.StatusOK)
		return nil
	}
	return checkLoginWithParams(sessionManager, usersCollection, forOffer(offersCollection, handler))
}

func forOffer(offersCollection db.Offers, handler HandlerWithUserAndOffer) HandlerWithParamsWithUser {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params, user *model.User) *router.HandlerError {
		idString := ps.ByName("id")
		if !bson.IsObjectIdHex(idString) {
			return router.NewStringHandlerError("invalid offer ID", "", http.StatusBadRequest)
		}
		id := bson.ObjectIdHex(idString)
		offer, err := offersCollection.GetID(id)
		if err != nil {
			return router.NewHandlerError(err, "Couldn't find an offer with this ID", http.StatusNotFound)
		}
		return handler(w, r, user, offer)
	}
}

// postOfferToFB forms a post, sends it to FB and returns the post's FB ID
func postOfferToFB(offer model.Offer, user *model.User, restaurant *model.Restaurant, api facebook.API) (string, *router.HandlerError) {
	if restaurant.FacebookPageID == "" {
		return "", nil
	}
	message := formFBOfferMessage(offer)
	post, err := api.PagePublish(user.Session.FacebookPageToken, restaurant.FacebookPageID, message)
	if err != nil {
		return "", router.NewHandlerError(err, "Failed to post the offer to Facebook", http.StatusBadGateway)
	}
	return post.ID, nil
}

func formFBOfferMessage(o model.Offer) string {
	ingredients := strings.Join(o.Ingredients, ", ")
	capitalizedIngredients := capitalizeString(ingredients)
	return fmt.Sprintf("%s - %s", o.Title, capitalizedIngredients)
}

func capitalizeString(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToUpper(r)) + s[n:]
}

func parseOffer(r *http.Request, restaurant *model.Restaurant) (*model.OfferPOST, error) {
	var offer model.OfferPOST
	err := json.NewDecoder(r.Body).Decode(&offer)
	if err != nil {
		return nil, err
	}
	offer.Restaurant = model.OfferRestaurant{
		Name:     restaurant.Name,
		Region:   restaurant.Region,
		Address:  restaurant.Address,
		Location: restaurant.Location,
		Phone:    restaurant.Phone,
	}
	return &offer, nil
}

func getImageDataToChecksumMapper(imageStorage storage.Images) func(string) (string, error) {
	return func(imageData string) (string, error) {
		if imageData == "" {
			return "", nil
		}
		return parseAndStoreImage(imageData, imageStorage)
	}
}

func parseAndStoreImage(imageDataURL string, imageStorage storage.Images) (string, error) {
	imageChecksum, err := imageStorage.ChecksumDataURL(imageDataURL)
	if err != nil {
		return "", err
	}
	if err = imageStorage.StoreDataURL(imageDataURL); err != nil {
		return "", err
	}
	return imageChecksum, nil
}
<|endoftext|>


<commit_before>package models

// This file was generated by the swagger tool.
// Editing this file might prove futile when you re-run the swagger generate command

import (
	strfmt "github.com/go-swagger/go-swagger/strfmt"

	"github.com/go-swagger/go-swagger/errors"
	"github.com/go-swagger/go-swagger/httpkit/validate"
)

/*ChartFile chart file

swagger:model chartFile
*/
type ChartFile struct {

	/* checksum

	Required: true
	Min Length: 1
	*/
	Checksum string `json:"checksum"`

	/* created

	Required: true
	*/
	Created strfmt.DateTime `json:"created"`

	/* description

	Required: true
	Min Length: 1
	*/
	Description string `json:"description"`

	/* name

	Required: true
	Min Length: 1
	*/
	Name string `json:"name"`

	/* url

	Required: true
	Min Length: 1
	*/
	URL string `json:"url"`

	/* version

	Required: true
	Min Length: 1
	*/
	Version string `json:"version"`
}

// Validate validates this chart file
func (m *ChartFile) Validate(formats strfmt.Registry) error {
	var res []error

	if err := m.validateChecksum(formats); err != nil {
		// prop
		res = append(res, err)
	}

	if err := m.validateCreated(formats); err != nil {
		// prop
		res = append(res, err)
	}

	if err := m.validateDescription(formats); err != nil {
		// prop
		res = append(res, err)
	}

	if err := m.validateName(formats); err != nil {
		// prop
		res = append(res, err)
	}

	if err := m.validateURL(formats); err != nil {
		// prop
		res = append(res, err)
	}

	if err := m.validateVersion(formats); err != nil {
		// prop
		res = append(res, err)
	}

	if len(res) > 0 {
		return errors.CompositeValidationError(res...)
	}
	return nil
}

func (m *ChartFile) validateChecksum(formats strfmt.Registry) error {

	if err := validate.RequiredString("checksum", "body", string(m.Checksum)); err != nil {
		return err
	}

	if err := validate.MinLength("checksum", "body", string(m.Checksum), 1); err != nil {
		return err
	}

	return nil
}

func (m *ChartFile) validateCreated(formats strfmt.Registry) error {

	if err := validate.Required("created", "body", strfmt.DateTime(m.Created)); err != nil {
		return err
	}

	return nil
}

func (m *ChartFile) validateDescription(formats strfmt.Registry) error {

	if err := validate.RequiredString("description", "body", string(m.Description)); err != nil {
		return err
	}

	if err := validate.MinLength("description", "body", string(m.Description), 1); err != nil {
		return err
	}

	return nil
}

func (m *ChartFile) validateName(formats strfmt.Registry) error {

	if err := validate.RequiredString("name", "body", string(m.Name)); err != nil {
		return err
	}

	if err := validate.MinLength("name", "body", string(m.Name), 1); err != nil {
		return err
	}

	return nil
}

func (m *ChartFile) validateURL(formats strfmt.Registry) error {

	if err := validate.RequiredString("url", "body", string(m.URL)); err != nil {
		return err
	}

	if err := validate.MinLength("url", "body", string(m.URL), 1); err != nil {
		return err
	}

	return nil
}

func (m *ChartFile) validateVersion(formats strfmt.Registry) error {

	if err := validate.RequiredString("version", "body", string(m.Version)); err != nil {
		return err
	}

	if err := validate.MinLength("version", "body", string(m.Version), 1); err != nil {
		return err
	}

	return nil
}
<commit_msg>chore(swagger): removed not used chart_file.go<commit_after><|endoftext|>


<commit_before>// Copyright 2017 The Kubernetes 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 handler

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"os"
	"strings"

	"github.com/golang/glog"
	"golang.org/x/text/language"
)

const defaultDir = "./public/en"

// Localization is a spec for the localization configuration of dashboard.
type Localization struct {
	Translations []Translation `json:"translations"`
}

// Translation is a single translation definition spec.
type Translation struct {
	File string `json:"file"`
	Key  string `json:"key"`
}

// LocaleHandler serves different localized versions of the frontend application
// based on the Accept-Language header.
type LocaleHandler struct {
	SupportedLocales []language.Tag
}

// CreateLocaleHandler loads the localization configuration and constructs a LocaleHandler.
func CreateLocaleHandler() *LocaleHandler {
	locales, err := getSupportedLocales("./locale_conf.json")
	if err != nil {
		glog.Warningf("Error when loading the localization configuration. Dashboard will not be localized. %s", err)
		locales = []language.Tag{}
	}
	return &LocaleHandler{SupportedLocales: locales}
}

func getSupportedLocales(configFile string) ([]language.Tag, error) {
	// read config file
	localesFile, err := ioutil.ReadFile(configFile)
	if err != nil {
		return []language.Tag{}, err
	}

	// unmarshall
	localization := Localization{}
	err = json.Unmarshal(localesFile, &localization)
	if err != nil {
		glog.Warningf("%s %s", string(localesFile), err)
	}

	// filter locale keys
	result := []language.Tag{}
	for _, translation := range localization.Translations {
		result = append(result, language.Make(translation.Key))
	}
	return result, nil
}

// LocaleHandler serves different html versions based on the Accept-Language header.
func (handler *LocaleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.EscapedPath() == "/" || r.URL.EscapedPath() == "/index.html" {
		// Do not store the html page in the cache. If the user is to click on 'switch language',
		// we want a different index.html (for the right locale) to be served when the page refreshes.
		w.Header().Add("Cache-Control", "no-store")
	}
	acceptLanguage := r.Header.Get("Accept-Language")
	dirName := handler.determineLocalizedDir(acceptLanguage)
	http.FileServer(http.Dir(dirName)).ServeHTTP(w, r)
}

func (handler *LocaleHandler) determineLocalizedDir(locale string) string {
	tags, _, err := language.ParseAcceptLanguage(locale)
	if (err != nil) || (len(tags) == 0) {
		return defaultDir
	}

	locales := handler.SupportedLocales
	tag, _, confidence := language.NewMatcher(locales).Match(tags...)
	matchedLocale := strings.ToLower(tag.String())
	if confidence != language.Exact {
		matchedLocale = ""
		for _, l := range locales {
			base, _ := tag.Base()
			if l.String() == base.String() {
				matchedLocale = l.String()
			}
		}
	}

	localeDir := "./public/" + matchedLocale
	if matchedLocale != "" && handler.dirExists(localeDir) {
		return localeDir
	}
	return defaultDir
}

func (handler *LocaleHandler) dirExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			glog.Warningf(name)
			return false
		}
	}
	return true
}
<commit_msg>support-force-language (#2402)<commit_after>// Copyright 2017 The Kubernetes 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 handler

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"os"
	"strings"

	"github.com/golang/glog"
	"golang.org/x/text/language"
)

const defaultDir = "./public/en"

// Localization is a spec for the localization configuration of dashboard.
type Localization struct {
	Translations []Translation `json:"translations"`
}

// Translation is a single translation definition spec.
type Translation struct {
	File string `json:"file"`
	Key  string `json:"key"`
}

// LocaleHandler serves different localized versions of the frontend application
// based on the Accept-Language header.
type LocaleHandler struct {
	SupportedLocales []language.Tag
}

// CreateLocaleHandler loads the localization configuration and constructs a LocaleHandler.
func CreateLocaleHandler() *LocaleHandler {
	locales, err := getSupportedLocales("./locale_conf.json")
	if err != nil {
		glog.Warningf("Error when loading the localization configuration. Dashboard will not be localized. %s", err)
		locales = []language.Tag{}
	}
	return &LocaleHandler{SupportedLocales: locales}
}

func getSupportedLocales(configFile string) ([]language.Tag, error) {
	// read config file
	localesFile, err := ioutil.ReadFile(configFile)
	if err != nil {
		return []language.Tag{}, err
	}

	// unmarshall
	localization := Localization{}
	err = json.Unmarshal(localesFile, &localization)
	if err != nil {
		glog.Warningf("%s %s", string(localesFile), err)
	}

	// filter locale keys
	result := []language.Tag{}
	for _, translation := range localization.Translations {
		result = append(result, language.Make(translation.Key))
	}
	return result, nil
}

// LocaleHandler serves different html versions based on the Accept-Language header.
func (handler *LocaleHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.EscapedPath() == "/" || r.URL.EscapedPath() == "/index.html" {
		// Do not store the html page in the cache. If the user is to click on 'switch language',
		// we want a different index.html (for the right locale) to be served when the page refreshes.
		w.Header().Add("Cache-Control", "no-store")
	}
	acceptLanguage := os.Getenv("ACCEPT_LANGUAGE")
	if acceptLanguage == "" {
		acceptLanguage = r.Header.Get("Accept-Language")
	}
	dirName := handler.determineLocalizedDir(acceptLanguage)
	http.FileServer(http.Dir(dirName)).ServeHTTP(w, r)
}

func (handler *LocaleHandler) determineLocalizedDir(locale string) string {
	tags, _, err := language.ParseAcceptLanguage(locale)
	if (err != nil) || (len(tags) == 0) {
		return defaultDir
	}

	locales := handler.SupportedLocales
	tag, _, confidence := language.NewMatcher(locales).Match(tags...)
	matchedLocale := strings.ToLower(tag.String())
	if confidence != language.Exact {
		matchedLocale = ""
		for _, l := range locales {
			base, _ := tag.Base()
			if l.String() == base.String() {
				matchedLocale = l.String()
			}
		}
	}

	localeDir := "./public/" + matchedLocale
	if matchedLocale != "" && handler.dirExists(localeDir) {
		return localeDir
	}
	return defaultDir
}

func (handler *LocaleHandler) dirExists(name string) bool {
	if _, err := os.Stat(name); err != nil {
		if os.IsNotExist(err) {
			glog.Warningf(name)
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package datastructure

import (
	"fmt"
	"container/list"
	"reflect"
)

const defaultMaxSize = 100

type hashTableChaining struct {
	maxSize int
	size int
	data []*list.List
}

type item struct {
	key Value
	value Value
}

func NewHashTableChaining(maxSize int) Map {
	if maxSize <= 0 {
		maxSize = defaultMaxSize
	}
	return &hashTableChaining{
		maxSize: maxSize,
		size: 0,
		data: make([]*list.List, maxSize),
	}
}

func (h *hashTableChaining) Put(key Value, value Value) Value {
	index := h.getIndex(key)
	if h.data[index] == nil {
		// Put as new
		l := list.New()
		l.PushBack(&item{key: key, value: value})
		h.data[index] = l
		h.size++
	} else {
		l := h.data[index]
		found := false
		for e := l.Front(); e != nil; e = e.Next() {
			if i := e.Value.(*item); reflect.DeepEqual(i.key.Get(), key.Get()) {
				found = true
				break
			}
		}
		if !found {
			l.PushBack(&item{key: key, value: value})
			h.size++
		}
		//for e := l.Front(); e != nil; e = e.Next() {
		//	if i := e.Value.(*e); i.key.Get() == key.Get() {
		//		// Replace an old e with new one
		//		l.Remove(e)
		//		l.PushBack(&e{key: key, value: value})
		//		h.size++
		//		return i.value
		//	}
		//}
	}
	return nil
}

func (h *hashTableChaining) Get(key Value) (Value, error) {
	index := h.getIndex(key)
	if h.data[index] == nil {
		return nil, fmt.Errorf("not found")
	}
	list := h.data[index]
	for e := list.Front(); e != nil; e = e.Next() {
		if i := e.Value.(*item); i.key.Get() == key.Get() {
			return i.value, nil
		}
	}
	return nil, fmt.Errorf("not found")
}

func (h *hashTableChaining) Remove(key Value) (Value, error) {
	index := h.getIndex(key)
	if h.data[index] == nil {
		return nil, fmt.Errorf("not found")
	}
	list := h.data[index]
	for e := list.Front(); e != nil; e = e.Next() {
		if i := e.Value.(*item); i.key.Get() == key.Get() {
			removed := list.Remove(e)
			h.size--
			return removed.(*item).value, nil
		}
	}
	return nil, fmt.Errorf("not found")
}

func (h *hashTableChaining) Size() int {
	return h.size
}

func (h *hashTableChaining) calculateHashCode(v Value) int {
	result := 0
	for _, s := range fmt.Sprint(v) {
		result += int(s)
	}
	return result
}

func (h *hashTableChaining) getIndex(key Value) int {
	k, ok := key.(Hashable)
	var hashCode int
	if ok {
		hashCode = k.HashCode()
	} else {
		hashCode = h.calculateHashCode(key)
	}
	return hashCode % h.maxSize
}
<commit_msg>Remove dead code<commit_after>package datastructure

import (
	"fmt"
	"container/list"
	"reflect"
)

const defaultMaxSize = 100

type hashTableChaining struct {
	maxSize int
	size int
	data []*list.List
}

type item struct {
	key Value
	value Value
}

func NewHashTableChaining(maxSize int) Map {
	if maxSize <= 0 {
		maxSize = defaultMaxSize
	}
	return &hashTableChaining{
		maxSize: maxSize,
		size: 0,
		data: make([]*list.List, maxSize),
	}
}

func (h *hashTableChaining) Put(key Value, value Value) Value {
	index := h.getIndex(key)
	if h.data[index] == nil {
		// Put as new
		l := list.New()
		l.PushBack(&item{key: key, value: value})
		h.data[index] = l
		h.size++
	} else {
		l := h.data[index]
		found := false
		for e := l.Front(); e != nil; e = e.Next() {
			if i := e.Value.(*item); reflect.DeepEqual(i.key.Get(), key.Get()) {
				found = true
				break
			}
		}
		if !found {
			l.PushBack(&item{key: key, value: value})
			h.size++
		}
	}
	return nil
}

func (h *hashTableChaining) Get(key Value) (Value, error) {
	index := h.getIndex(key)
	if h.data[index] == nil {
		return nil, fmt.Errorf("not found")
	}
	list := h.data[index]
	for e := list.Front(); e != nil; e = e.Next() {
		if i := e.Value.(*item); i.key.Get() == key.Get() {
			return i.value, nil
		}
	}
	return nil, fmt.Errorf("not found")
}

func (h *hashTableChaining) Remove(key Value) (Value, error) {
	index := h.getIndex(key)
	if h.data[index] == nil {
		return nil, fmt.Errorf("not found")
	}
	list := h.data[index]
	for e := list.Front(); e != nil; e = e.Next() {
		if i := e.Value.(*item); i.key.Get() == key.Get() {
			removed := list.Remove(e)
			h.size--
			return removed.(*item).value, nil
		}
	}
	return nil, fmt.Errorf("not found")
}

func (h *hashTableChaining) Size() int {
	return h.size
}

func (h *hashTableChaining) calculateHashCode(v Value) int {
	result := 0
	for _, s := range fmt.Sprint(v) {
		result += int(s)
	}
	return result
}

func (h *hashTableChaining) getIndex(key Value) int {
	k, ok := key.(Hashable)
	var hashCode int
	if ok {
		hashCode = k.HashCode()
	} else {
		hashCode = h.calculateHashCode(key)
	}
	return hashCode % h.maxSize
}
<|endoftext|>


<commit_before>package database

import (
	"fmt"
	"strings"

	"github.com/banzaicloud/bank-vaults/vault"
	vaultapi "github.com/hashicorp/vault/api"
	"github.com/pkg/errors"
)

// DynamicSecretDataSource creates a SQL data source but instead of passing username:password
// in the connection source, one just has to pass in a Vault role name:
//     ds, err := DynamicSecretDataSource("mysql", "my-role@localhost:3306/dbname?parseTime=True")
//
// MySQL (github.com/go-sql-driver/mysql) and PostgreSQL URI is supported.
//
// The underlying Vault client will make sure that the credential is renewed when it
// is close to the time of expiry.
func DynamicSecretDataSource(dialect string, source string) (dynamicSecretDataSource string, err error) {

	postgresql := false
	if strings.HasPrefix(source, "postgresql://") {
		source = strings.TrimPrefix(source, "postgresql://")
		postgresql = true
	}

	sourceParts := strings.Split(source, "@")
	if len(sourceParts) != 2 {
		err = errors.New("invalid database source")
		return "", err
	}

	vaultRole := sourceParts[0]
	vaultCredsEndpoint := "database/creds/" + vaultRole

	vaultClient, err := vault.NewClient(vaultRole)

	if err != nil {
		err = errors.Wrap(err, "failed to establish vault connection")
		return "", err
	}

	secret, err := vaultClient.Vault().Logical().Read(vaultCredsEndpoint)
	if err != nil {
		err = errors.Wrap(err, "failed to read db credentials")
		return "", err
	}

	if secret == nil {
		err = errors.New("failed to find '" + vaultCredsEndpoint + "' secret in vault")
		return "", err
	}

	secretRenewer, err := vaultClient.Vault().NewRenewer(&vaultapi.RenewerInput{Secret: secret})
	go secretRenewer.Renew()

	username := secret.Data["username"].(string)
	password := secret.Data["password"].(string)

	dynamicSecretDataSource = fmt.Sprintf("%s:%s@%s", username, password, sourceParts[1])
	if postgresql {
		dynamicSecretDataSource = "postgresql://" + source
	}

	return dynamicSecretDataSource, nil
}
<commit_msg>Add vault.NewRenewer() error handling<commit_after>package database

import (
	"fmt"
	"strings"

	"github.com/banzaicloud/bank-vaults/vault"
	vaultapi "github.com/hashicorp/vault/api"
	"github.com/pkg/errors"
)

// DynamicSecretDataSource creates a SQL data source but instead of passing username:password
// in the connection source, one just has to pass in a Vault role name:
//     ds, err := DynamicSecretDataSource("mysql", "my-role@localhost:3306/dbname?parseTime=True")
//
// MySQL (github.com/go-sql-driver/mysql) and PostgreSQL URI is supported.
//
// The underlying Vault client will make sure that the credential is renewed when it
// is close to the time of expiry.
func DynamicSecretDataSource(dialect string, source string) (dynamicSecretDataSource string, err error) {

	postgresql := false
	if strings.HasPrefix(source, "postgresql://") {
		source = strings.TrimPrefix(source, "postgresql://")
		postgresql = true
	}

	sourceParts := strings.Split(source, "@")
	if len(sourceParts) != 2 {
		err = errors.New("invalid database source")
		return "", err
	}

	vaultRole := sourceParts[0]
	vaultCredsEndpoint := "database/creds/" + vaultRole

	vaultClient, err := vault.NewClient(vaultRole)

	if err != nil {
		err = errors.Wrap(err, "failed to establish vault connection")
		return "", err
	}

	secret, err := vaultClient.Vault().Logical().Read(vaultCredsEndpoint)
	if err != nil {
		err = errors.Wrap(err, "failed to read db credentials")
		return "", err
	}

	if secret == nil {
		err = errors.New("failed to find '" + vaultCredsEndpoint + "' secret in vault")
		return "", err
	}

	secretRenewer, err := vaultClient.Vault().NewRenewer(&vaultapi.RenewerInput{Secret: secret})

	if err != nil {
		vaultClient.Close()
		err = errors.Wrap(err, "failed to start db credential renewer")
		return "", err
	}

	go secretRenewer.Renew()

	username := secret.Data["username"].(string)
	password := secret.Data["password"].(string)

	dynamicSecretDataSource = fmt.Sprintf("%s:%s@%s", username, password, sourceParts[1])
	if postgresql {
		dynamicSecretDataSource = "postgresql://" + source
	}

	return dynamicSecretDataSource, nil
}
<|endoftext|>


<commit_before>package projects

import (
	"archive/zip"
	"bytes"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

type Project struct {
	Name       string
	Owner      string
	Repository string
	Version    string
	Identifier string
	Domain     string
	Subdomain  string
	Type       string
}

func (p Project) Path() string {
	var buffer bytes.Buffer

	if p.Type == "static" {
		buffer.WriteString("/srv/")
		buffer.WriteString(p.Domain)
		buffer.WriteString("/")
		buffer.WriteString(p.Subdomain)
		buffer.WriteString("/")
	}

	return string(buffer.Bytes())
}

func (p Project) ArchivePath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.TemporaryPath())
	buffer.WriteString(p.Version)
	buffer.WriteString(".zip")

	return string(buffer.Bytes())
}

func (p Project) TemporaryPath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.Path()[:len(p.Path())-1])
	buffer.WriteString(".milou/")

	return string(buffer.Bytes())
}

func (p Project) ExtractPath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.TemporaryPath())
	buffer.WriteString(p.Subdomain)

	return string(buffer.Bytes())
}

func (p Project) ArchiveLocation() string {
	var buffer bytes.Buffer

	buffer.WriteString("https://s3.amazonaws.com/")
	buffer.WriteString(p.Identifier)
	buffer.WriteString("/")
	buffer.WriteString(p.Version)
	buffer.WriteString(".zip")

	return string(buffer.Bytes())
}

func (p Project) Extract() error {
	r, err := zip.OpenReader(p.ArchivePath())
	if err != nil {
		return err
	}
	defer r.Close()

	for _, f := range r.File {
		rc, err := f.Open()
		if err != nil {
			return err
		}
		defer rc.Close()

		fpath := filepath.Join(p.ExtractPath(), f.Name)
		if f.FileInfo().IsDir() {
			os.MkdirAll(fpath, f.Mode())
		} else {
			var fdir string
			if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 {
				fdir = fpath[:lastIndex]
			}

			err = os.MkdirAll(fdir, f.Mode())
			if err != nil {
				log.Fatal(err)
				return err
			}
			f, err := os.OpenFile(
				fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}
			defer f.Close()

			_, err = io.Copy(f, rc)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (p Project) Download() {
	response, err := http.Get(p.ArchiveLocation())
	defer response.Body.Close()

	if err != nil {
		log.Fatal(err)
	}

	archive, err := os.Create(p.ArchivePath())
	defer archive.Close()

	_, err = io.Copy(archive, response.Body)

	if err != nil {
		log.Fatal(err)
	}
}

func (p Project) Place() {
	err := os.RemoveAll(p.Path())

	if err != nil {
		log.Fatal(err)
	}

	err = os.Rename(p.ExtractPath(), p.Path())

	if err != nil {
		log.Fatal(err)
	}
}

func (p Project) Prepare() {
	if _, err := os.Stat(p.TemporaryPath()); os.IsNotExist(err) {
		err = os.MkdirAll(p.TemporaryPath(), 0700)

		if err != nil {
			log.Fatal(err)
		}
	}
}

func (p Project) CleanUp() {
	err := os.RemoveAll(p.TemporaryPath())

	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Glued it all together with a function to deploy a project<commit_after>package projects

import (
	"archive/zip"
	"bytes"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

type Project struct {
	Name       string
	Owner      string
	Repository string
	Version    string
	Identifier string
	Domain     string
	Subdomain  string
	Type       string
}

func (p Project) Path() string {
	var buffer bytes.Buffer

	if p.Type == "static" {
		buffer.WriteString("/srv/")
		buffer.WriteString(p.Domain)
		buffer.WriteString("/")
		buffer.WriteString(p.Subdomain)
		buffer.WriteString("/")
	}

	return string(buffer.Bytes())
}

func (p Project) ArchivePath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.TemporaryPath())
	buffer.WriteString(p.Version)
	buffer.WriteString(".zip")

	return string(buffer.Bytes())
}

func (p Project) TemporaryPath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.Path()[:len(p.Path())-1])
	buffer.WriteString(".milou/")

	return string(buffer.Bytes())
}

func (p Project) ExtractPath() string {
	var buffer bytes.Buffer

	buffer.WriteString(p.TemporaryPath())
	buffer.WriteString(p.Subdomain)

	return string(buffer.Bytes())
}

func (p Project) ArchiveLocation() string {
	var buffer bytes.Buffer

	buffer.WriteString("https://s3.amazonaws.com/")
	buffer.WriteString(p.Identifier)
	buffer.WriteString("/")
	buffer.WriteString(p.Version)
	buffer.WriteString(".zip")

	return string(buffer.Bytes())
}

func (p Project) Extract() error {
	r, err := zip.OpenReader(p.ArchivePath())
	if err != nil {
		return err
	}
	defer r.Close()

	for _, f := range r.File {
		rc, err := f.Open()
		if err != nil {
			return err
		}
		defer rc.Close()

		fpath := filepath.Join(p.ExtractPath(), f.Name)
		if f.FileInfo().IsDir() {
			os.MkdirAll(fpath, f.Mode())
		} else {
			var fdir string
			if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 {
				fdir = fpath[:lastIndex]
			}

			err = os.MkdirAll(fdir, f.Mode())
			if err != nil {
				log.Fatal(err)
				return err
			}
			f, err := os.OpenFile(
				fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}
			defer f.Close()

			_, err = io.Copy(f, rc)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (p Project) Download() {
	response, err := http.Get(p.ArchiveLocation())
	defer response.Body.Close()

	if err != nil {
		log.Fatal(err)
	}

	archive, err := os.Create(p.ArchivePath())
	defer archive.Close()

	_, err = io.Copy(archive, response.Body)

	if err != nil {
		log.Fatal(err)
	}
}

func (p Project) Place() {
	err := os.RemoveAll(p.Path())

	if err != nil {
		log.Fatal(err)
	}

	err = os.Rename(p.ExtractPath(), p.Path())

	if err != nil {
		log.Fatal(err)
	}
}

func (p Project) Prepare() {
	if _, err := os.Stat(p.TemporaryPath()); os.IsNotExist(err) {
		err = os.MkdirAll(p.TemporaryPath(), 0700)

		if err != nil {
			log.Fatal(err)
		}
	}
}

func (p Project) CleanUp() {
	err := os.RemoveAll(p.TemporaryPath())

	if err != nil {
		log.Fatal(err)
	}
}

func (p Project) Deploy() {
	p.Prepare()
	p.Download()
	p.Extract()
	p.Place()
	p.CleanUp()
}
<|endoftext|>


<commit_before>/*
 * Copyright 2012 Nan Deng
 *
 * 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 proto

import (
	"testing"
	"fmt"
	"bytes"
	"io"
	"crypto/rand"
	"crypto/sha256"
	"crypto/hmac"
)

type opBetweenWriteAndRead interface {
	Op()
}

func testSendingCommands(t *testing.T, op opBetweenWriteAndRead, compress, encrypt bool, from, to *commandIO, cmds ...*command) {
	errCh := make(chan error)
	startRead := make(chan bool)
	go func() {
		defer close(errCh)
		if op != nil {
			<-startRead
		}
		for i, cmd := range cmds {
			recved, err := to.ReadCommand()
			if err != nil {
				errCh <- err
				return
			}
			if !cmd.eq(recved) {
				errCh <- fmt.Errorf("%vth command does not equal", i)
			}
		}
	}()

	for _, cmd := range cmds {
		err := from.WriteCommand(cmd, compress, encrypt)
		if err != nil {
			t.Errorf("Error on write: %v", err)
		}
	}
	if op != nil {
		op.Op()
		startRead<-true
	}

	for err := range errCh {
		if err != nil {
			t.Errorf("Error on read: %v", err)
		}
	}
}

func getBufferCommandIOs(t *testing.T) (io1, io2 *commandIO, buffer *bytes.Buffer, ks *keySet) {
	keybuf := make([]byte, 2 * (authKeyLen + encrKeyLen))
	io.ReadFull(rand.Reader, keybuf)
	sen := keybuf[:encrKeyLen]
	keybuf = keybuf[encrKeyLen:]
	sau := keybuf[:authKeyLen]
	keybuf = keybuf[authKeyLen:]
	cen := keybuf[:encrKeyLen]
	keybuf = keybuf[encrKeyLen:]
	cau := keybuf[:authKeyLen]
	keybuf = keybuf[authKeyLen:]

	buffer = new(bytes.Buffer)
	ks = newKeySet(sen, sau, cen, cau)
	scmdio := ks.getServerCommandIO(buffer)
	ccmdio := ks.getClientCommandIO(buffer)
	io1 = scmdio
	io2 = ccmdio
	return
}

func getNetworkCommandIOs(t *testing.T) (io1, io2 *commandIO) {
	sks, cks, s2c, c2s := exchangeKeysOrReport(t)
	if sks == nil || cks == nil || s2c == nil || c2s == nil {
		return
	}

	scmdio := sks.getServerCommandIO(s2c)
	ccmdio := cks.getClientCommandIO(c2s)
	io1 = scmdio
	io2 = ccmdio
	return
}

func TestExchangingFullCommandNoCompressNoEncrypt(t *testing.T) {
	cmd := randomCommand()
	compress := false
	encrypt := false
	io1, io2 := getNetworkCommandIOs(t)
	testSendingCommands(t, nil, compress, encrypt, io1, io2, cmd)
	testSendingCommands(t, nil, compress, encrypt, io2, io1, cmd)
}

func TestExchangingFullCommandNoEncrypt(t *testing.T) {
	cmd := randomCommand()
	compress := true
	encrypt := false
	io1, io2 := getNetworkCommandIOs(t)
	testSendingCommands(t, nil, compress, encrypt, io1, io2, cmd)
	testSendingCommands(t, nil, compress, encrypt, io2, io1, cmd)
}

type bufPrinter struct {
	buf *bytes.Buffer
	authKey []byte
}

func (self *bufPrinter) Op() {
	fmt.Printf("--------------\n")
	fmt.Printf("Data in buffer: %v\n", self.buf.Bytes())

	data := self.buf.Bytes()
	data = data[16:]

	hash := hmac.New(sha256.New, self.authKey)
	hash.Write(data)

	fmt.Printf("HMAC: %v\n", hash.Sum(nil))
	fmt.Printf("--------------\n")
}

func TestExchangingFullCommandOverNetwork(t *testing.T) {
	cmd := randomCommand()
	compress := true
	encrypt := true
	io1, io2 := getNetworkCommandIOs(t)
	testSendingCommands(t, nil, compress, encrypt, io1, io2, cmd)
	testSendingCommands(t, nil, compress, encrypt, io2, io1, cmd)
}

func TestExchangingFullCommandInBuffer(t *testing.T) {
	cmd := randomCommand()
	compress := true
	encrypt := true
	io1, io2 := getNetworkCommandIOs(t)
	io1, io2, buffer, ks := getBufferCommandIOs(t)
	op := &bufPrinter{buffer, ks.serverAuthKey}
	testSendingCommands(t, op, compress, encrypt, io1, io2, cmd)

	op = &bufPrinter{buffer, ks.clientAuthKey}
	testSendingCommands(t, op, compress, encrypt, io2, io1, cmd)
}

func randomCommand() *command {
	cmd := new(command)
	cmd.Type = 1
	cmd.Params = make([][]byte, 2)
	cmd.Params[0] = []byte{1,2,3}
	cmd.Params[1] = []byte{2,2,3}
	cmd.Message = new(Message)
	cmd.Message.Header = make(map[string]string, 2)
	cmd.Message.Header["a"] = "hello"
	cmd.Message.Header["b"] = "hell"
	cmd.Message.Body = make([]byte, 10)
	io.ReadFull(rand.Reader, cmd.Message.Body)
	return cmd
}

func TestExchangingMultiFullCommandOverNetwork(t *testing.T) {
	cmds := make([]*command, 100)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}

	compress := true
	encrypt := true
	io1, io2 := getNetworkCommandIOs(t)
	testSendingCommands(t, nil, compress, encrypt, io1, io2, cmds...)
	testSendingCommands(t, nil, compress, encrypt, io2, io1, cmds...)
}

func TestConcurrentWrite(t *testing.T) {
	N := 100
	cmds := make([]*command, N)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}
	io1, io2 := getNetworkCommandIOs(nil)
	done := make(chan bool)
	go func() {
		defer close(done)
		for i := 0; i < N; i++ {
			io2.ReadCommand()
		}
	}()

	for _, cmd := range cmds {
		go io1.WriteCommand(cmd, true, true)
	}
	<-done
}

func BenchmarkExchangingMultiFullCommandOverNetwork(b *testing.B) {
	b.StopTimer()
	cmds := make([]*command, b.N)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}
	io1, io2 := getNetworkCommandIOs(nil)
	done := make(chan bool)
	go func() {
		defer close(done)
		for i := 0; i < b.N; i++ {
			io2.ReadCommand()
		}
	}()

	b.StartTimer()
	for _, cmd := range cmds {
		io1.WriteCommand(cmd, true, true)
	}
	<-done
}

func BenchmarkExchangingMultiFullCommandNoEncrypt(b *testing.B) {
	b.StopTimer()
	cmds := make([]*command, b.N)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}
	io1, io2 := getNetworkCommandIOs(nil)
	done := make(chan bool)
	go func() {
		defer close(done)
		for i := 0; i < b.N; i++ {
			io2.ReadCommand()
		}
	}()

	b.StartTimer()
	for _, cmd := range cmds {
		io1.WriteCommand(cmd, true, false)
	}
	<-done
}

func BenchmarkExchangingMultiFullCommandNoEncryptNoCompress(b *testing.B) {
	b.StopTimer()
	cmds := make([]*command, b.N)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}
	io1, io2 := getNetworkCommandIOs(nil)
	done := make(chan bool)
	go func() {
		defer close(done)
		for i := 0; i < b.N; i++ {
			io2.ReadCommand()
		}
	}()

	b.StartTimer()
	for _, cmd := range cmds {
		io1.WriteCommand(cmd, false, false)
	}
	<-done
}
<commit_msg>Add one more test case<commit_after>/*
 * Copyright 2012 Nan Deng
 *
 * 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 proto

import (
	"testing"
	"fmt"
	"bytes"
	"io"
	"crypto/rand"
	"crypto/sha256"
	"crypto/hmac"
)

type opBetweenWriteAndRead interface {
	Op()
}

func testSendingCommands(t *testing.T, op opBetweenWriteAndRead, compress, encrypt bool, from, to *commandIO, cmds ...*command) {
	errCh := make(chan error)
	startRead := make(chan bool)
	go func() {
		defer close(errCh)
		if op != nil {
			<-startRead
		}
		for i, cmd := range cmds {
			recved, err := to.ReadCommand()
			if err != nil {
				errCh <- err
				return
			}
			if !cmd.eq(recved) {
				errCh <- fmt.Errorf("%vth command does not equal", i)
			}
		}
	}()

	for _, cmd := range cmds {
		err := from.WriteCommand(cmd, compress, encrypt)
		if err != nil {
			t.Errorf("Error on write: %v", err)
		}
	}
	if op != nil {
		op.Op()
		startRead<-true
	}

	for err := range errCh {
		if err != nil {
			t.Errorf("Error on read: %v", err)
		}
	}
}

func getBufferCommandIOs(t *testing.T) (io1, io2 *commandIO, buffer *bytes.Buffer, ks *keySet) {
	keybuf := make([]byte, 2 * (authKeyLen + encrKeyLen))
	io.ReadFull(rand.Reader, keybuf)
	sen := keybuf[:encrKeyLen]
	keybuf = keybuf[encrKeyLen:]
	sau := keybuf[:authKeyLen]
	keybuf = keybuf[authKeyLen:]
	cen := keybuf[:encrKeyLen]
	keybuf = keybuf[encrKeyLen:]
	cau := keybuf[:authKeyLen]
	keybuf = keybuf[authKeyLen:]

	buffer = new(bytes.Buffer)
	ks = newKeySet(sen, sau, cen, cau)
	scmdio := ks.getServerCommandIO(buffer)
	ccmdio := ks.getClientCommandIO(buffer)
	io1 = scmdio
	io2 = ccmdio
	return
}

func getNetworkCommandIOs(t *testing.T) (io1, io2 *commandIO) {
	sks, cks, s2c, c2s := exchangeKeysOrReport(t)
	if sks == nil || cks == nil || s2c == nil || c2s == nil {
		return
	}

	scmdio := sks.getServerCommandIO(s2c)
	ccmdio := cks.getClientCommandIO(c2s)
	io1 = scmdio
	io2 = ccmdio
	return
}

func TestExchangingFullCommandNoCompressNoEncrypt(t *testing.T) {
	cmd := randomCommand()
	compress := false
	encrypt := false
	io1, io2 := getNetworkCommandIOs(t)
	testSendingCommands(t, nil, compress, encrypt, io1, io2, cmd)
	testSendingCommands(t, nil, compress, encrypt, io2, io1, cmd)
}

func TestExchangingFullCommandNoCompress(t *testing.T) {
	cmd := randomCommand()
	compress := false
	encrypt := true
	io1, io2 := getNetworkCommandIOs(t)
	testSendingCommands(t, nil, compress, encrypt, io1, io2, cmd)
	testSendingCommands(t, nil, compress, encrypt, io2, io1, cmd)
}

func TestExchangingFullCommandNoEncrypt(t *testing.T) {
	cmd := randomCommand()
	compress := true
	encrypt := false
	io1, io2 := getNetworkCommandIOs(t)
	testSendingCommands(t, nil, compress, encrypt, io1, io2, cmd)
	testSendingCommands(t, nil, compress, encrypt, io2, io1, cmd)
}

type bufPrinter struct {
	buf *bytes.Buffer
	authKey []byte
}

func (self *bufPrinter) Op() {
	fmt.Printf("--------------\n")
	fmt.Printf("Data in buffer: %v\n", self.buf.Bytes())

	data := self.buf.Bytes()
	data = data[16:]

	hash := hmac.New(sha256.New, self.authKey)
	hash.Write(data)

	fmt.Printf("HMAC: %v\n", hash.Sum(nil))
	fmt.Printf("--------------\n")
}

func TestExchangingFullCommandOverNetwork(t *testing.T) {
	cmd := randomCommand()
	compress := true
	encrypt := true
	io1, io2 := getNetworkCommandIOs(t)
	testSendingCommands(t, nil, compress, encrypt, io1, io2, cmd)
	testSendingCommands(t, nil, compress, encrypt, io2, io1, cmd)
}

func TestExchangingFullCommandInBuffer(t *testing.T) {
	cmd := randomCommand()
	compress := true
	encrypt := true
	io1, io2 := getNetworkCommandIOs(t)
	io1, io2, buffer, ks := getBufferCommandIOs(t)
	op := &bufPrinter{buffer, ks.serverAuthKey}
	testSendingCommands(t, op, compress, encrypt, io1, io2, cmd)

	op = &bufPrinter{buffer, ks.clientAuthKey}
	testSendingCommands(t, op, compress, encrypt, io2, io1, cmd)
}

func randomCommand() *command {
	cmd := new(command)
	cmd.Type = 1
	cmd.Params = make([][]byte, 2)
	cmd.Params[0] = []byte{1,2,3}
	cmd.Params[1] = []byte{2,2,3}
	cmd.Message = new(Message)
	cmd.Message.Header = make(map[string]string, 2)
	cmd.Message.Header["a"] = "hello"
	cmd.Message.Header["b"] = "hell"
	cmd.Message.Body = make([]byte, 10)
	io.ReadFull(rand.Reader, cmd.Message.Body)
	return cmd
}

func TestExchangingMultiFullCommandOverNetwork(t *testing.T) {
	cmds := make([]*command, 100)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}

	compress := true
	encrypt := true
	io1, io2 := getNetworkCommandIOs(t)
	testSendingCommands(t, nil, compress, encrypt, io1, io2, cmds...)
	testSendingCommands(t, nil, compress, encrypt, io2, io1, cmds...)
}

func TestConcurrentWrite(t *testing.T) {
	N := 100
	cmds := make([]*command, N)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}
	io1, io2 := getNetworkCommandIOs(nil)
	done := make(chan bool)
	go func() {
		defer close(done)
		for i := 0; i < N; i++ {
			io2.ReadCommand()
		}
	}()

	for _, cmd := range cmds {
		go io1.WriteCommand(cmd, true, true)
	}
	<-done
}

func BenchmarkExchangingMultiFullCommandOverNetwork(b *testing.B) {
	b.StopTimer()
	cmds := make([]*command, b.N)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}
	io1, io2 := getNetworkCommandIOs(nil)
	done := make(chan bool)
	go func() {
		defer close(done)
		for i := 0; i < b.N; i++ {
			io2.ReadCommand()
		}
	}()

	b.StartTimer()
	for _, cmd := range cmds {
		io1.WriteCommand(cmd, true, true)
	}
	<-done
}

func BenchmarkExchangingMultiFullCommandNoEncrypt(b *testing.B) {
	b.StopTimer()
	cmds := make([]*command, b.N)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}
	io1, io2 := getNetworkCommandIOs(nil)
	done := make(chan bool)
	go func() {
		defer close(done)
		for i := 0; i < b.N; i++ {
			io2.ReadCommand()
		}
	}()

	b.StartTimer()
	for _, cmd := range cmds {
		io1.WriteCommand(cmd, true, false)
	}
	<-done
}

func BenchmarkExchangingMultiFullCommandNoEncryptNoCompress(b *testing.B) {
	b.StopTimer()
	cmds := make([]*command, b.N)
	for i, _ := range cmds {
		cmd := randomCommand()
		cmds[i] = cmd
	}
	io1, io2 := getNetworkCommandIOs(nil)
	done := make(chan bool)
	go func() {
		defer close(done)
		for i := 0; i < b.N; i++ {
			io2.ReadCommand()
		}
	}()

	b.StartTimer()
	for _, cmd := range cmds {
		io1.WriteCommand(cmd, false, false)
	}
	<-done
}
<|endoftext|>


<commit_before>package aws

import (
	"bufio"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/eks"
	"github.com/aws/aws-sdk-go/service/eks/eksiface"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/stelligent/mu/common"
	"github.com/stelligent/mu/templates"
	yaml "gopkg.in/yaml.v2"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/rest"
)

const (
	v1Prefix        = "k8s-aws-v1."
	clusterIDHeader = "x-k8s-aws-id"
)

type eksKubernetesResourceManagerProvider struct {
	eksAPI            eksiface.EKSAPI
	stsAPI            *sts.STS
	extensionsManager common.ExtensionsManager
	dryrunPath        string
}

type eksKubernetesResourceManager struct {
	name              string
	client            dynamic.Interface
	extensionsManager common.ExtensionsManager
	dryrunPath        string
}

func newEksKubernetesResourceManagerProvider(sess *session.Session, extensionsManager common.ExtensionsManager, dryrunPath string) (common.KubernetesResourceManagerProvider, error) {
	if dryrunPath != "" {
		log.Debugf("Running in DRYRUN mode with path '%s'", dryrunPath)
	}
	log.Debug("Connecting to EKS service")
	eksAPI := eks.New(sess)
	stsAPI := sts.New(sess)

	return &eksKubernetesResourceManagerProvider{
		eksAPI:            eksAPI,
		stsAPI:            stsAPI,
		dryrunPath:        dryrunPath,
		extensionsManager: extensionsManager,
	}, nil
}

// GetResourceManager get a connection to eks cluster
func (eksMgrProvider *eksKubernetesResourceManagerProvider) GetResourceManager(name string) (common.KubernetesResourceManager, error) {
	eksAPI := eksMgrProvider.eksAPI
	stsAPI := eksMgrProvider.stsAPI

	resp, err := eksAPI.DescribeCluster(&eks.DescribeClusterInput{
		Name: aws.String(name),
	})

	if err != nil {
		return nil, err
	}

	certData, err := base64.StdEncoding.DecodeString(aws.StringValue(resp.Cluster.CertificateAuthority.Data))
	if err != nil {
		return nil, err
	}

	request, _ := stsAPI.GetCallerIdentityRequest(&sts.GetCallerIdentityInput{})
	request.HTTPRequest.Header.Add(clusterIDHeader, name)

	// sign the request
	presignedURLString, err := request.Presign(60 * time.Second)
	if err != nil {
		return nil, err
	}
	token := v1Prefix + base64.RawURLEncoding.EncodeToString([]byte(presignedURLString))

	k8sClientConfig := &rest.Config{
		Host: aws.StringValue(resp.Cluster.Endpoint),
		TLSClientConfig: rest.TLSClientConfig{
			CAData:   certData,
			Insecure: false,
		},
		BearerToken: token,
	}

	client, err := dynamic.NewForConfig(k8sClientConfig)
	if err != nil {
		return nil, err
	}

	return &eksKubernetesResourceManager{
		name:              name,
		client:            client,
		dryrunPath:        eksMgrProvider.dryrunPath,
		extensionsManager: eksMgrProvider.extensionsManager,
	}, nil
}

// UpsertResources for create/update of resources in k8s cluster
func (eksMgr *eksKubernetesResourceManager) UpsertResources(templateName string,
	templateData interface{}) error {

	// apply new values
	templateBody, err := templates.GetAsset(templateName,
		templates.DecorateTemplate(eksMgr.extensionsManager, ""),
		templates.ExecuteTemplate(templateData))

	if err != nil {
		return err
	}

	scanner := bufio.NewScanner(strings.NewReader(templateBody))
	var b strings.Builder
	for scanner.Scan() {
		if scanner.Text() == "---" {
			// flush current resource
			if b.Len() > 0 {
				err = eksMgr.upsertResource(b.String())
				if err != nil {
					return err
				}
				b.Reset()
			}
		} else {
			// append to current resource
			_, err = fmt.Fprintln(&b, scanner.Text())
			if err != nil {
				return err
			}
		}
	}

	if b.Len() > 0 {
		err = eksMgr.upsertResource(b.String())
		if err != nil {
			return err
		}
	}
	return nil
}

func newResourceStub(resourceBody string) (*resourceStub, error) {
	stub := &resourceStub{}
	err := yaml.Unmarshal([]byte(resourceBody), stub)
	if err != nil {
		return nil, err
	}
	return stub, nil
}

type resourceStub struct {
	APIVersion string `yaml:"apiVersion,omitempty"`
	Kind       string
	Metadata   struct {
		Name      string
		Namespace string
	}
}

func (eksMgr *eksKubernetesResourceManager) upsertResource(resourceBody string) error {
	stub, err := newResourceStub(resourceBody)
	if err != nil {
		return err
	}

	resourceKind := stub.Kind
	resourceNamespace := stub.Metadata.Namespace
	resourceName := stub.Metadata.Name

	resourceClient, err := eksMgr.getResourceInterface(stub.APIVersion, stub.Kind)
	if err != nil {
		return nil
	}

	// load existing resource for eks
	exists := true
	options := metav1.GetOptions{}
	_, err = resourceClient.Namespace(resourceNamespace).Get(resourceName, options)

	if err != nil {
		if errors.IsNotFound(err) {
			exists = false
		} else {
			return err
		}
	}

	resourceURN := fmt.Sprintf("%s-%s-%s", eksMgr.name, resourceKind, resourceName)
	resourceBody, err = templates.DecorateTemplate(eksMgr.extensionsManager, resourceURN)("", resourceBody)
	if err != nil {
		return err
	}

	resource := &unstructured.Unstructured{Object: make(map[string]interface{})}
	yaml.Unmarshal([]byte(resourceBody), resource.Object)
	resource.Object = common.ConvertMapI2MapS(resource.Object).(map[string]interface{})

	if eksMgr.dryrunPath != "" {
		err := writeResource(eksMgr.dryrunPath, resourceURN, resourceBody)
		if err != nil {
			return err
		}
		var action string
		if exists {
			action = "update"
		} else {
			action = "create"
		}
		log.Infof("  DRYRUN: Skipping %s of resource named '%s'.  File written to '%s'", action, resourceURN, eksMgr.dryrunPath)
		return nil
	}

	if exists {
		log.Infof("  Patching namespace:%s type:%s name:%s", resourceNamespace, resourceKind, resourceName)
		s, err := json.Marshal(resource.UnstructuredContent())
		if err != nil {
			return err
		}
		if _, err := resourceClient.Namespace(resourceNamespace).Patch(resourceName, types.StrategicMergePatchType, s); err != nil {
			return err
		}
	} else {
		log.Infof("  Creating namespace:%s type:%s name:%s", resourceNamespace, resourceKind, resourceName)
		if _, err := resourceClient.Namespace(resourceNamespace).Create(resource); err != nil {
			return err
		}
	}

	return nil
}

// DeleteResource for deletion of resources in k8s cluster
func (eksMgr *eksKubernetesResourceManager) DeleteResource(apiVersion string, kind string, namespace string, name string) error {
	resourceClient, err := eksMgr.getResourceInterface(apiVersion, kind)
	if err != nil {
		return nil
	}
	return resourceClient.Namespace(namespace).Delete(name, nil)
}

// List resources in k8s cluster
func (eksMgr *eksKubernetesResourceManager) ListResources(apiVersion string, kind string, namespace string) (*unstructured.UnstructuredList, error) {
	resourceClient, err := eksMgr.getResourceInterface(apiVersion, kind)
	if err != nil {
		return nil, nil
	}
	return resourceClient.Namespace(namespace).List(metav1.ListOptions{})
}

func (eksMgr *eksKubernetesResourceManager) getResourceInterface(apiVersion string, kind string) (dynamic.NamespaceableResourceInterface, error) {
	groupVersion, err := schema.ParseGroupVersion(apiVersion)
	if err != nil {
		return nil, err
	}
	groupVersionKind := groupVersion.WithKind(kind)
	resourceGroupVersion, _ := meta.UnsafeGuessKindToResource(groupVersionKind)
	return eksMgr.client.Resource(resourceGroupVersion), nil
}

func writeResource(directory string, resourceName string, resourceBody string) error {
	os.MkdirAll(directory, 0700)
	resourceFile := fmt.Sprintf("%s/resource-%s.yml", directory, resourceName)
	fileWriter, err := os.Create(resourceFile)
	if err != nil {
		return err
	}
	defer fileWriter.Close()
	l, err := fileWriter.WriteString(resourceBody)
	log.Debugf("Wrote %d bytes to %s", l, resourceFile)
	return err
}
<commit_msg>Issue #393 - Fix dry run error when provider is EKS<commit_after>package aws

import (
	"bufio"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"os"
	"strings"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/types"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/eks"
	"github.com/aws/aws-sdk-go/service/eks/eksiface"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/stelligent/mu/common"
	"github.com/stelligent/mu/templates"
	yaml "gopkg.in/yaml.v2"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/rest"
)

const (
	v1Prefix        = "k8s-aws-v1."
	clusterIDHeader = "x-k8s-aws-id"
)

type eksKubernetesResourceManagerProvider struct {
	eksAPI            eksiface.EKSAPI
	stsAPI            *sts.STS
	extensionsManager common.ExtensionsManager
	dryrunPath        string
}

type eksKubernetesResourceManager struct {
	name              string
	client            dynamic.Interface
	extensionsManager common.ExtensionsManager
	dryrunPath        string
}

func newEksKubernetesResourceManagerProvider(sess *session.Session, extensionsManager common.ExtensionsManager, dryrunPath string) (common.KubernetesResourceManagerProvider, error) {
	if dryrunPath != "" {
		log.Debugf("Running in DRYRUN mode with path '%s'", dryrunPath)
	}
	log.Debug("Connecting to EKS service")
	eksAPI := eks.New(sess)
	stsAPI := sts.New(sess)

	return &eksKubernetesResourceManagerProvider{
		eksAPI:            eksAPI,
		stsAPI:            stsAPI,
		dryrunPath:        dryrunPath,
		extensionsManager: extensionsManager,
	}, nil
}

func dryRunRestConfig() dynamic.Interface {
	restConfig, _ := dynamic.NewForConfig(&rest.Config{
		Host: "",
		TLSClientConfig: rest.TLSClientConfig{
			CAData:   []byte(""),
			Insecure: false,
		},
		BearerToken: "",
	})
	return restConfig
}

// GetResourceManager get a connection to eks cluster
func (eksMgrProvider *eksKubernetesResourceManagerProvider) GetResourceManager(name string) (common.KubernetesResourceManager, error) {
	eksAPI := eksMgrProvider.eksAPI
	stsAPI := eksMgrProvider.stsAPI

	if eksMgrProvider.dryrunPath != "" {
		return &eksKubernetesResourceManager{
			name:              name,
			client:            dryRunRestConfig(),
			dryrunPath:        eksMgrProvider.dryrunPath,
			extensionsManager: eksMgrProvider.extensionsManager,
		}, nil
	}

	resp, err := eksAPI.DescribeCluster(&eks.DescribeClusterInput{
		Name: aws.String(name),
	})

	if err != nil {
		return nil, err
	}

	certData, err := base64.StdEncoding.DecodeString(aws.StringValue(resp.Cluster.CertificateAuthority.Data))
	if err != nil {
		return nil, err
	}

	request, _ := stsAPI.GetCallerIdentityRequest(&sts.GetCallerIdentityInput{})
	request.HTTPRequest.Header.Add(clusterIDHeader, name)

	// sign the request
	presignedURLString, err := request.Presign(60 * time.Second)
	if err != nil {
		return nil, err
	}
	token := v1Prefix + base64.RawURLEncoding.EncodeToString([]byte(presignedURLString))

	k8sClientConfig := &rest.Config{
		Host: aws.StringValue(resp.Cluster.Endpoint),
		TLSClientConfig: rest.TLSClientConfig{
			CAData:   certData,
			Insecure: false,
		},
		BearerToken: token,
	}

	client, err := dynamic.NewForConfig(k8sClientConfig)
	if err != nil {
		return nil, err
	}

	return &eksKubernetesResourceManager{
		name:              name,
		client:            client,
		dryrunPath:        eksMgrProvider.dryrunPath,
		extensionsManager: eksMgrProvider.extensionsManager,
	}, nil
}

// UpsertResources for create/update of resources in k8s cluster
func (eksMgr *eksKubernetesResourceManager) UpsertResources(templateName string,
	templateData interface{}) error {

	if eksMgr.dryrunPath != "" {
		return nil
	}

	// apply new values
	templateBody, err := templates.GetAsset(templateName,
		templates.DecorateTemplate(eksMgr.extensionsManager, ""),
		templates.ExecuteTemplate(templateData))

	if err != nil {
		return err
	}

	scanner := bufio.NewScanner(strings.NewReader(templateBody))
	var b strings.Builder
	for scanner.Scan() {
		if scanner.Text() == "---" {
			// flush current resource
			if b.Len() > 0 {
				err = eksMgr.upsertResource(b.String())
				if err != nil {
					return err
				}
				b.Reset()
			}
		} else {
			// append to current resource
			_, err = fmt.Fprintln(&b, scanner.Text())
			if err != nil {
				return err
			}
		}
	}

	if b.Len() > 0 {
		err = eksMgr.upsertResource(b.String())
		if err != nil {
			return err
		}
	}
	return nil
}

func newResourceStub(resourceBody string) (*resourceStub, error) {
	stub := &resourceStub{}
	err := yaml.Unmarshal([]byte(resourceBody), stub)
	if err != nil {
		return nil, err
	}
	return stub, nil
}

type resourceStub struct {
	APIVersion string `yaml:"apiVersion,omitempty"`
	Kind       string
	Metadata   struct {
		Name      string
		Namespace string
	}
}

func (eksMgr *eksKubernetesResourceManager) upsertResource(resourceBody string) error {
	stub, err := newResourceStub(resourceBody)
	if err != nil {
		return err
	}

	resourceKind := stub.Kind
	resourceNamespace := stub.Metadata.Namespace
	resourceName := stub.Metadata.Name

	resourceClient, err := eksMgr.getResourceInterface(stub.APIVersion, stub.Kind)
	if err != nil {
		return nil
	}

	// load existing resource for eks
	exists := true
	options := metav1.GetOptions{}
	_, err = resourceClient.Namespace(resourceNamespace).Get(resourceName, options)

	if err != nil {
		if errors.IsNotFound(err) {
			exists = false
		} else {
			return err
		}
	}

	resourceURN := fmt.Sprintf("%s-%s-%s", eksMgr.name, resourceKind, resourceName)
	resourceBody, err = templates.DecorateTemplate(eksMgr.extensionsManager, resourceURN)("", resourceBody)
	if err != nil {
		return err
	}

	resource := &unstructured.Unstructured{Object: make(map[string]interface{})}
	yaml.Unmarshal([]byte(resourceBody), resource.Object)
	resource.Object = common.ConvertMapI2MapS(resource.Object).(map[string]interface{})

	if eksMgr.dryrunPath != "" {
		err := writeResource(eksMgr.dryrunPath, resourceURN, resourceBody)
		if err != nil {
			return err
		}
		var action string
		if exists {
			action = "update"
		} else {
			action = "create"
		}
		log.Infof("  DRYRUN: Skipping %s of resource named '%s'.  File written to '%s'", action, resourceURN, eksMgr.dryrunPath)
		return nil
	}

	if exists {
		log.Infof("  Patching namespace:%s type:%s name:%s", resourceNamespace, resourceKind, resourceName)
		s, err := json.Marshal(resource.UnstructuredContent())
		if err != nil {
			return err
		}
		if _, err := resourceClient.Namespace(resourceNamespace).Patch(resourceName, types.StrategicMergePatchType, s); err != nil {
			return err
		}
	} else {
		log.Infof("  Creating namespace:%s type:%s name:%s", resourceNamespace, resourceKind, resourceName)
		if _, err := resourceClient.Namespace(resourceNamespace).Create(resource); err != nil {
			return err
		}
	}

	return nil
}

// DeleteResource for deletion of resources in k8s cluster
func (eksMgr *eksKubernetesResourceManager) DeleteResource(apiVersion string, kind string, namespace string, name string) error {
	resourceClient, err := eksMgr.getResourceInterface(apiVersion, kind)
	if err != nil {
		return nil
	}
	return resourceClient.Namespace(namespace).Delete(name, nil)
}

// List resources in k8s cluster
func (eksMgr *eksKubernetesResourceManager) ListResources(apiVersion string, kind string, namespace string) (*unstructured.UnstructuredList, error) {
	resourceClient, err := eksMgr.getResourceInterface(apiVersion, kind)
	if err != nil {
		return nil, nil
	}
	return resourceClient.Namespace(namespace).List(metav1.ListOptions{})
}

func (eksMgr *eksKubernetesResourceManager) getResourceInterface(apiVersion string, kind string) (dynamic.NamespaceableResourceInterface, error) {
	groupVersion, err := schema.ParseGroupVersion(apiVersion)
	if err != nil {
		return nil, err
	}
	groupVersionKind := groupVersion.WithKind(kind)
	resourceGroupVersion, _ := meta.UnsafeGuessKindToResource(groupVersionKind)
	return eksMgr.client.Resource(resourceGroupVersion), nil
}

func writeResource(directory string, resourceName string, resourceBody string) error {
	os.MkdirAll(directory, 0700)
	resourceFile := fmt.Sprintf("%s/resource-%s.yml", directory, resourceName)
	fileWriter, err := os.Create(resourceFile)
	if err != nil {
		return err
	}
	defer fileWriter.Close()
	l, err := fileWriter.WriteString(resourceBody)
	log.Debugf("Wrote %d bytes to %s", l, resourceFile)
	return err
}
<|endoftext|>


<commit_before>package provider

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/Nitro/sidecar/catalog"
	"github.com/Nitro/sidecar/service"
	"github.com/containous/flaeg"
	"github.com/containous/traefik/log"
	"github.com/containous/traefik/safe"
	"github.com/containous/traefik/types"
	fsnotify "gopkg.in/fsnotify.v1"
)

const (
	method                      = "wrr"
	weight                      = 0
	sticky                      = false
	defaultMaxConnAmount        = 2000
	defaultMaxConnExtractorFunc = "request.host"
)

var (
	// Disable all timeouts for watcher requests
	watcherHTTPTransport = &http.Transport{ResponseHeaderTimeout: 0}
	watcherHTTPClient    = &http.Client{
		Timeout:   0,
		Transport: watcherHTTPTransport,
	}

	sidecarHTTPClient = &http.Client{
		Timeout: 5 * time.Second,
	}
)

var _ Provider = (*Sidecar)(nil)

// Sidecar holds configurations of the Sidecar provider
type Sidecar struct {
	BaseProvider      `mapstructure:",squash"`
	Endpoint          string `description:"Sidecar URL"`
	configurationChan chan<- types.ConfigMessage
	RefreshConn       flaeg.Duration `description:"How often to refresh the connection to Sidecar backend"`
	connTimer         *time.Timer
}

type sidecarFrontend struct {
	*types.Frontend
	MaxConn *types.MaxConn `json:"maxConn,omitempty"`
}

type sidecarConfig struct {
	Frontends map[string]*sidecarFrontend `json:"frontends,omitempty"`
}

type callback func(map[string][]*service.Service, error)

// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Sidecar) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
	provider.configurationChan = configurationChan

	if provider.Watch {
		pool.Go(
			func(stop chan bool) {
				provider.sidecarWatcher(stop, pool)
			},
		)

		watcher, err := fsnotify.NewWatcher()
		if err != nil {
			log.Errorln("Error creating file watcher", err)
			return err
		}

		file, err := os.Open(provider.Filename)
		if err != nil {
			log.Errorln("Error opening file", err)
			return err
		}
		defer file.Close()
		pool.Go(func(stop chan bool) {
			defer watcher.Close()
			for {
				select {
				case <-stop:
					return
				case event := <-watcher.Events:
					if strings.Contains(event.Name, file.Name()) {
						log.Debug("Sidecar Frontend File event:", event)
						states, errState := provider.fetchState()
						if errState != nil {
							log.Errorln("Error reloading Sidecar config", errState)
						}
						errLoadConfig := provider.loadSidecarConfig(states)
						if errLoadConfig != nil {
							log.Errorf("Error loading Sidecar config: %s", errLoadConfig)
						}
					}
				case errWatcher := <-watcher.Errors:
					log.Errorln("Watcher event error", errWatcher)
				}
			}
		})
		err = watcher.Add(filepath.Dir(file.Name()))
		if err != nil {
			log.Error("Error adding file watcher", err)
			return err
		}
	}
	states, err := provider.fetchState()
	if err != nil {
		log.Fatalln("Error reloading Sidecar config", err)
	}

	return provider.loadSidecarConfig(states)
}

func (provider *Sidecar) constructConfig(sidecarStates *catalog.ServicesState) (*types.Configuration, error) {
	log.Infoln("loading sidecar config")
	sidecarConfig := types.Configuration{
		Backends:  provider.makeBackends(sidecarStates),
		Frontends: map[string]*types.Frontend{},
	}

	frontends, err := provider.makeFrontends()
	if err != nil {
		return nil, err
	}

	for name, frontend := range frontends {
		sidecarConfig.Frontends[name] = frontend.Frontend

		if backend, ok := sidecarConfig.Backends[name]; ok {
			backend.MaxConn = frontend.MaxConn
			if backend.MaxConn == nil {
				backend.MaxConn = &types.MaxConn{
					Amount:        defaultMaxConnAmount,
					ExtractorFunc: defaultMaxConnExtractorFunc,
				}
			} else {
				if backend.MaxConn.Amount == 0 {
					backend.MaxConn.Amount = defaultMaxConnAmount
				}

				if backend.MaxConn.ExtractorFunc == "" {
					backend.MaxConn.ExtractorFunc = defaultMaxConnExtractorFunc
				}
			}

		}
	}

	return &sidecarConfig, nil
}

func (provider *Sidecar) loadSidecarConfig(sidecarStates *catalog.ServicesState) error {
	conf, err := provider.constructConfig(sidecarStates)
	if err != nil {
		return err
	}

	provider.configurationChan <- types.ConfigMessage{
		ProviderName:  "sidecar",
		Configuration: conf,
	}
	return nil
}

func (provider *Sidecar) sidecarWatcher(stop chan bool, pool *safe.Pool) {
	//set timeout to be just a bit more than connection refresh interval
	provider.connTimer = time.NewTimer(time.Duration(provider.RefreshConn))

	log.Debugf("Using %s Sidecar connection refresh interval", provider.RefreshConn)
	provider.recycleConn(stop, pool)
}

func (provider *Sidecar) recycleConn(stop chan bool, pool *safe.Pool) {
	var err error
	var resp *http.Response
	var req *http.Request
	for {
		select {
		case <-stop:
			return
		default:
			//use refresh interval to occasionally reconnect to Sidecar in case the stream connection is lost
			req, err = http.NewRequest("GET", provider.Endpoint+"/watch?by_service=false", nil)
			if err != nil {
				log.Errorf("Error creating http request to Sidecar: %s, Error: %s", provider.Endpoint, err)
				continue
			}
			resp, err = watcherHTTPClient.Do(req)
			if err != nil {
				log.Errorf("Error connecting to Sidecar: %s, Error: %s", provider.Endpoint, err)
				time.Sleep(5 * time.Second)
				continue
			}

			safe.Go(func() { decodeStream(resp.Body, provider.callbackLoader) })

			//wait on refresh connection timer.  If this expires we haven't seen an update in a
			//while and should cancel the request, reset the time, and reconnect just in case
			<-provider.connTimer.C
			provider.connTimer.Reset(time.Duration(provider.RefreshConn))

			//TODO: Deprecated method. Refactor this to use a context.
			watcherHTTPTransport.CancelRequest(req)
		}
	}
}

func (provider *Sidecar) callbackLoader(sidecarStates *catalog.ServicesState, err error) {
	//load config regardless
	errLoadConfig := provider.loadSidecarConfig(sidecarStates)
	if errLoadConfig != nil {
		log.Error("Error reloading config: %s", errLoadConfig)
	}

	if err != nil {
		return
	}

	//else reset connection timer
	if !provider.connTimer.Stop() {
		<-provider.connTimer.C
	}

	provider.connTimer.Reset(time.Duration(provider.RefreshConn))
}

func (provider *Sidecar) makeFrontends() (map[string]*sidecarFrontend, error) {
	configuration := new(sidecarConfig)
	if _, err := toml.DecodeFile(provider.Filename, configuration); err != nil {
		log.Errorf("Error reading file: %s", err)
		return nil, err
	}

	return configuration.Frontends, nil
}

func (provider *Sidecar) makeBackends(sidecarStates *catalog.ServicesState) map[string]*types.Backend {
	sidecarBacks := make(map[string]*types.Backend)

	sidecarStates.EachService(
		func(hostname *string, serviceId *string, svc *service.Service) {
			var backend *types.Backend
			var ok bool
			if backend, ok = sidecarBacks[svc.Name]; !ok {
				backend = &types.Backend{
					LoadBalancer: &types.LoadBalancer{Method: method, Sticky: sticky},
					Servers:      make(map[string]types.Server),
				}

				sidecarBacks[svc.Name] = backend
			}

			if svc.IsAlive() {
				for _, port := range svc.Ports {
					host := port.IP
					if host == "" {
						ipAddr, err := net.LookupIP(svc.Hostname)
						if err != nil {
							log.Errorf("Error resolving IP address for host '%s': %s", svc.Hostname, err)
							continue

						} else {
							host = ipAddr[0].String()
						}
					}

					// A service can expose multiple ports on the same host
					name := fmt.Sprintf("%s_%d", svc.Hostname, port.Port)
					backend.Servers[name] = types.Server{
						URL: fmt.Sprintf("http://%s:%d", host, port.Port),
					}
				}
			}
		},
	)

	return sidecarBacks
}

func (provider *Sidecar) fetchState() (*catalog.ServicesState, error) {
	resp, err := sidecarHTTPClient.Get(provider.Endpoint + "/state.json")
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	state, err := catalog.Decode(bytes)
	if err != nil {
		return nil, err
	}
	return state, nil
}

func decodeStream(input io.Reader, callback func(*catalog.ServicesState, error)) error {
	dec := json.NewDecoder(input)
	for dec.More() {
		var conf catalog.ServicesState
		err := dec.Decode(&conf)

		callback(&conf, err)

		if err != nil {
			log.Errorf("Error decoding stream: %s", err)
			return err
		}
	}
	return nil
}
<commit_msg>Make sure that the IP received from Sidecar is valid<commit_after>package provider

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/Nitro/sidecar/catalog"
	"github.com/Nitro/sidecar/service"
	"github.com/containous/flaeg"
	"github.com/containous/traefik/log"
	"github.com/containous/traefik/safe"
	"github.com/containous/traefik/types"
	fsnotify "gopkg.in/fsnotify.v1"
)

const (
	method                      = "wrr"
	weight                      = 0
	sticky                      = false
	defaultMaxConnAmount        = 2000
	defaultMaxConnExtractorFunc = "request.host"
)

var (
	// Disable all timeouts for watcher requests
	watcherHTTPTransport = &http.Transport{ResponseHeaderTimeout: 0}
	watcherHTTPClient    = &http.Client{
		Timeout:   0,
		Transport: watcherHTTPTransport,
	}

	sidecarHTTPClient = &http.Client{
		Timeout: 5 * time.Second,
	}
)

var _ Provider = (*Sidecar)(nil)

// Sidecar holds configurations of the Sidecar provider
type Sidecar struct {
	BaseProvider      `mapstructure:",squash"`
	Endpoint          string `description:"Sidecar URL"`
	configurationChan chan<- types.ConfigMessage
	RefreshConn       flaeg.Duration `description:"How often to refresh the connection to Sidecar backend"`
	connTimer         *time.Timer
}

type sidecarFrontend struct {
	*types.Frontend
	MaxConn *types.MaxConn `json:"maxConn,omitempty"`
}

type sidecarConfig struct {
	Frontends map[string]*sidecarFrontend `json:"frontends,omitempty"`
}

type callback func(map[string][]*service.Service, error)

// Provide allows the provider to provide configurations to traefik
// using the given configuration channel.
func (provider *Sidecar) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints types.Constraints) error {
	provider.configurationChan = configurationChan

	if provider.Watch {
		pool.Go(
			func(stop chan bool) {
				provider.sidecarWatcher(stop, pool)
			},
		)

		watcher, err := fsnotify.NewWatcher()
		if err != nil {
			log.Errorln("Error creating file watcher", err)
			return err
		}

		file, err := os.Open(provider.Filename)
		if err != nil {
			log.Errorln("Error opening file", err)
			return err
		}
		defer file.Close()
		pool.Go(func(stop chan bool) {
			defer watcher.Close()
			for {
				select {
				case <-stop:
					return
				case event := <-watcher.Events:
					if strings.Contains(event.Name, file.Name()) {
						log.Debug("Sidecar Frontend File event:", event)
						states, errState := provider.fetchState()
						if errState != nil {
							log.Errorln("Error reloading Sidecar config", errState)
						}
						errLoadConfig := provider.loadSidecarConfig(states)
						if errLoadConfig != nil {
							log.Errorf("Error loading Sidecar config: %s", errLoadConfig)
						}
					}
				case errWatcher := <-watcher.Errors:
					log.Errorln("Watcher event error", errWatcher)
				}
			}
		})
		err = watcher.Add(filepath.Dir(file.Name()))
		if err != nil {
			log.Error("Error adding file watcher", err)
			return err
		}
	}
	states, err := provider.fetchState()
	if err != nil {
		log.Fatalln("Error reloading Sidecar config", err)
	}

	return provider.loadSidecarConfig(states)
}

func (provider *Sidecar) constructConfig(sidecarStates *catalog.ServicesState) (*types.Configuration, error) {
	log.Infoln("loading sidecar config")
	sidecarConfig := types.Configuration{
		Backends:  provider.makeBackends(sidecarStates),
		Frontends: map[string]*types.Frontend{},
	}

	frontends, err := provider.makeFrontends()
	if err != nil {
		return nil, err
	}

	for name, frontend := range frontends {
		sidecarConfig.Frontends[name] = frontend.Frontend

		if backend, ok := sidecarConfig.Backends[name]; ok {
			backend.MaxConn = frontend.MaxConn
			if backend.MaxConn == nil {
				backend.MaxConn = &types.MaxConn{
					Amount:        defaultMaxConnAmount,
					ExtractorFunc: defaultMaxConnExtractorFunc,
				}
			} else {
				if backend.MaxConn.Amount == 0 {
					backend.MaxConn.Amount = defaultMaxConnAmount
				}

				if backend.MaxConn.ExtractorFunc == "" {
					backend.MaxConn.ExtractorFunc = defaultMaxConnExtractorFunc
				}
			}

		}
	}

	return &sidecarConfig, nil
}

func (provider *Sidecar) loadSidecarConfig(sidecarStates *catalog.ServicesState) error {
	conf, err := provider.constructConfig(sidecarStates)
	if err != nil {
		return err
	}

	provider.configurationChan <- types.ConfigMessage{
		ProviderName:  "sidecar",
		Configuration: conf,
	}
	return nil
}

func (provider *Sidecar) sidecarWatcher(stop chan bool, pool *safe.Pool) {
	//set timeout to be just a bit more than connection refresh interval
	provider.connTimer = time.NewTimer(time.Duration(provider.RefreshConn))

	log.Debugf("Using %s Sidecar connection refresh interval", provider.RefreshConn)
	provider.recycleConn(stop, pool)
}

func (provider *Sidecar) recycleConn(stop chan bool, pool *safe.Pool) {
	var err error
	var resp *http.Response
	var req *http.Request
	for {
		select {
		case <-stop:
			return
		default:
			//use refresh interval to occasionally reconnect to Sidecar in case the stream connection is lost
			req, err = http.NewRequest("GET", provider.Endpoint+"/watch?by_service=false", nil)
			if err != nil {
				log.Errorf("Error creating http request to Sidecar: %s, Error: %s", provider.Endpoint, err)
				continue
			}
			resp, err = watcherHTTPClient.Do(req)
			if err != nil {
				log.Errorf("Error connecting to Sidecar: %s, Error: %s", provider.Endpoint, err)
				time.Sleep(5 * time.Second)
				continue
			}

			safe.Go(func() { decodeStream(resp.Body, provider.callbackLoader) })

			//wait on refresh connection timer.  If this expires we haven't seen an update in a
			//while and should cancel the request, reset the time, and reconnect just in case
			<-provider.connTimer.C
			provider.connTimer.Reset(time.Duration(provider.RefreshConn))

			//TODO: Deprecated method. Refactor this to use a context.
			watcherHTTPTransport.CancelRequest(req)
		}
	}
}

func (provider *Sidecar) callbackLoader(sidecarStates *catalog.ServicesState, err error) {
	//load config regardless
	errLoadConfig := provider.loadSidecarConfig(sidecarStates)
	if errLoadConfig != nil {
		log.Error("Error reloading config: %s", errLoadConfig)
	}

	if err != nil {
		return
	}

	//else reset connection timer
	if !provider.connTimer.Stop() {
		<-provider.connTimer.C
	}

	provider.connTimer.Reset(time.Duration(provider.RefreshConn))
}

func (provider *Sidecar) makeFrontends() (map[string]*sidecarFrontend, error) {
	configuration := new(sidecarConfig)
	if _, err := toml.DecodeFile(provider.Filename, configuration); err != nil {
		log.Errorf("Error reading file: %s", err)
		return nil, err
	}

	return configuration.Frontends, nil
}

func (provider *Sidecar) makeBackends(sidecarStates *catalog.ServicesState) map[string]*types.Backend {
	sidecarBacks := make(map[string]*types.Backend)

	sidecarStates.EachService(
		func(hostname *string, serviceId *string, svc *service.Service) {
			var backend *types.Backend
			var ok bool
			if backend, ok = sidecarBacks[svc.Name]; !ok {
				backend = &types.Backend{
					LoadBalancer: &types.LoadBalancer{Method: method, Sticky: sticky},
					Servers:      make(map[string]types.Server),
				}

				sidecarBacks[svc.Name] = backend
			}

			if svc.IsAlive() {
				for _, port := range svc.Ports {
					var host string
					parsedIP := net.ParseIP(port.IP)
					if parsedIP == nil {
						// Try to resolve the hostname if we don't get a valid IP from Sidecar
						ipAddr, err := net.LookupIP(svc.Hostname)
						if err != nil {
							log.Errorf("Error resolving IP address for host '%s': %s", svc.Hostname, err)
							continue
						} else {
							host = ipAddr[0].String()
						}
					} else {
						host = parsedIP.String()
					}

					// A service can expose multiple ports on the same host
					name := fmt.Sprintf("%s_%d", svc.Hostname, port.Port)
					backend.Servers[name] = types.Server{
						URL: fmt.Sprintf("http://%s:%d", host, port.Port),
					}
				}
			}
		},
	)

	return sidecarBacks
}

func (provider *Sidecar) fetchState() (*catalog.ServicesState, error) {
	resp, err := sidecarHTTPClient.Get(provider.Endpoint + "/state.json")
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	state, err := catalog.Decode(bytes)
	if err != nil {
		return nil, err
	}
	return state, nil
}

func decodeStream(input io.Reader, callback func(*catalog.ServicesState, error)) error {
	dec := json.NewDecoder(input)
	for dec.More() {
		var conf catalog.ServicesState
		err := dec.Decode(&conf)

		callback(&conf, err)

		if err != nil {
			log.Errorf("Error decoding stream: %s", err)
			return err
		}
	}
	return nil
}
<|endoftext|>