<commit_before>// Originally inspired by:
// http://www.spaceacademy.net.au/spacelab/projects/jovrad/jovrad.htm,
// but extended and updated with some external libraries.
//
// Copyright 2016 Jeremy Bingham, under the MIT License.
// See the LICENSE file in this repository, or
// http://www.opensource.org/licenses/MIT

/*
jovian-noise is a program that will forecast possible upcoming Jupiter decameter radio storms, which you can hear on a shortwave radio (best between 18MHz and 23MHz), with a suitable receiver and antenna.

This program should be reasonably accurate, but there's always a possibility that the event will fail to materialize for one reason or another - possibly a bug with the program, possibly a bug with Jupiter itself.

This was originally inspired by a QBASIC program at http://www.spaceacademy.net.au/spacelab/projects/jovrad/jovrad.htm, but uses external libraries for many of the calculations and can optionally limit the returned results to when Jupiter will be above the horizon at your location.

To run this program, you will need to obtain the VSOP87 files for planet locations (an archive is located at ftp://cdsarc.u-strasbg.fr/pub/cats/VI%2F81/) and place them in a directory somewhere. The environment variable VSOP87 must be set to the path of the directory with the VSOP87 files.

    Usage of ./jovian-noise:
      -duration duration
       	    Duration (in golang ParseDuration format) from the start time to calculate the forecast (default 720h0m0s)
      -interval int
    	    Interval in minutes to calculate the forecast (default 30)
      -lat int
    	    Optional latitute. If given, will limit results to when Jupiter is above the horizon at this location. Requires -lon
      -lon int
    	    Optional longitude. If given, will limit results to when Jupiter is above the horizon at this location. Requires -lat
      -start-time string
    	    Start time (in RFC 3339 format) to calculate Jupiter radio storm forecasts (defaults to now)

Credits

Many web pages went into getting this together. The most immediately useful for this program were:

* http://www.spaceacademy.net.au/spacelab/projects/jovrad/jovrad.htm

* http://www.projectpluto.com/grs_form.htm

* https://github.com/akkana/scripts/blob/master/jsjupiter/jupiter.js

More Information about Jupiter amateur radio astronomy

* http://www.radiosky.com/rjcentral.html

* http://www.thrushobservatory.org/radio.htm

* http://radiojove.gsfc.nasa.gov/ -- NASA's Radio JOVE Project

License

Copyright 2016, Jeremy Bingham, under the terms of the MIT License.

*/
package main

import (
	"flag"
	"fmt"
	"log"
	"math"
	"os"
	"time"
	"github.com/soniakeys/meeus/elliptic"
	"github.com/soniakeys/meeus/sidereal"
	"github.com/soniakeys/meeus/globe"
	"github.com/soniakeys/meeus/rise"
	sexa "github.com/soniakeys/sexagesimal"
	"github.com/soniakeys/meeus/julian"
	pp "github.com/soniakeys/meeus/planetposition"
)

var months = []string{
	"",
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec",
}

const version string = "0.1.5"

func main() {
	startTime := flag.String("start-time", "", "Start time (in RFC 3339 format) to calculate Jupiter radio storm forecasts (defaults to now)")
	dur := flag.Duration("duration", 30 * 24 * time.Hour, "Duration (in golang ParseDuration format) from the start time to calculate the forecast")
	interval := flag.Int("interval", 30, "Interval in minutes to calculate the forecast")
	lat := flag.Int("lat", 0, "Optional latitute. If given, will limit results to when Jupiter is above the horizon at this location. Requires -lon")
	lon := flag.Int("lon", 0, "Optional longitude. If given, will limit results to when Jupiter is above the horizon at this location. Requires -lat")
	ver := flag.Bool("version", false, "Print version number and exit.")

	var t time.Time
	flag.Parse()

	if *ver {
		fmt.Printf("jovian-noise version %s\n", version)
		os.Exit(0)
	}

	if *startTime == "" {
		t = time.Now().UTC()
	} else {
		var err error
		t, err = time.Parse(time.RFC3339, *startTime)
		if err != nil {
			log.Println(err)
			os.Exit(1)
		}
		t = t.UTC()
	}
	if *lat != 0 && *lon == 0 || *lat == 0 && *lon != 0 {
		log.Println("Both -lat and -lon, or neither, must be supplied")
		os.Exit(1)
	}
	var risen bool
	var coords globe.Coord
	if *lat != 0 && *lat != 0 {
		coords.Lon = sexa.NewAngle(false, *lon, 0, 0).Rad()
		coords.Lat = sexa.NewAngle(false, *lat, 0, 0).Rad()
		risen = true
	}
	earth, err := pp.LoadPlanet(pp.Earth)
	if err != nil {
		panic(err)
	}
	jupiter, err := pp.LoadPlanet(pp.Jupiter)
	if err != nil {
		panic(err)
	}
	endTime := t.Add(*dur)
	fmt.Printf("################################################################################\n")
	fmt.Printf("\t\tJovian Decameter Radio Storm Forcast for:\n")
	fmt.Printf("\t\t    %s\n", t)
	fmt.Printf("\t\t\t\tuntil:\n\t\t    %s\n", endTime)
	lz, loff := t.Local().Zone()
	fmt.Printf("\t\t     Local time zone: %s (%05d)\n", lz, (loff / 60 / 60) * 100)
	if *lat != 0 && *lon != 0 {
		fmt.Printf("\t\t   --- For coordinates %dº, %dº ---\n", *lat, *lon)
	}
	fmt.Printf("################################################################################\n")
	fmt.Printf("DY | Date  | HH:MM (UTC) | HH:MM (local) | Io Phase | CML    | Dist(AU) | source\n")
	fmt.Printf("--------------------------------------------------------------------------------\n")

	fmtStr := "%3d  %s %2d  %02d:%02d         %02d:%02d           %6.2f     %6.2f   %4.2f       %s\n"
	for t.Before(endTime) {
		jd := julian.TimeToJD(t)
		var skip bool
		if risen {
			// round the day off
			rounded := t.Round(24 * time.Hour)
			if rounded.After(t) {
				rounded = rounded.Add(-24 * time.Hour)
			}
			secs := float64(t.Sub(rounded) / time.Second)
			ra, dec := elliptic.Position(jupiter, earth, jd)
			th0 := sidereal.Apparent0UT(jd)
			h0 := rise.Stdh0Stellar
			rising, _, set, err := rise.ApproxTimes(coords, h0, th0, ra, dec)
			if err != nil {
				log.Println(err)
				skip = true
			}
			if rising > secs || secs > set {
				// Jupiter's not out!
				//log.Printf("no jupiter: secs %f rising %f set %f", secs, rising, set )
				skip = true
			}
		}
		if !skip {
			el, _, eDist := earth.Position(jd)
			jl, _, jDist := jupiter.Position(jd)
			meridian := systemIIIMeridian(jd)
			eLon := el * (180 / math.Pi)
			jLon := jl * (180 / math.Pi)
			dist := distance(eLon, eDist, jLon, jDist)
			ioPhase := ioPos(jd, dist)
			rSource := source(meridian, ioPhase)

			if rSource != "" {
				l := t.Local()
				fmt.Printf(fmtStr, t.YearDay(), months[t.Month()], t.Day(), t.Hour(), t.Minute(), l.Hour(), l.Minute(), ioPhase, meridian, dist, rSource)
			}
		}
		t = t.Add(time.Duration(*interval) * time.Minute)
	}
}

// from http://www.projectpluto.com/grs_form.htm
func meridianCorrection(jd float64) float64 {
	jupMean := (jd - 2455636.938) * 360 / 4332.89709
	eqnCenter := 5.55 * math.Sin(jupMean)
	angle := (jd - 2451870.628) * 360 / 398.884 - eqnCenter
	correction := 11 * math.Sin(angle) + 5 * math.Cos(angle) - 1.25 * math.Cos(jupMean) - eqnCenter
	return correction
}

func systemIIIMeridian(jd float64) float64 {
	correction := meridianCorrection(jd)
	mFull := 138.41 + 870.4535567 * jd + correction
	m := math.Mod(mFull, 360)
	return m
}

func ioPos(jd float64, dist float64) float64 {
	// snagged the equations for this from
	// https://github.com/akkana/scripts/blob/master/jsjupiter/jupiter.js
	d := jd - 2415020
	v := reg(134.63 + 0.00111587 * d * (math.Pi / 180));
	eAnomaly := reg((358.476 + 0.9856003 * d) * (math.Pi / 180))
	jAnomaly := reg((225.328 + 0.0830853 * d + 0.33 * math.Sin(v)) * (math.Pi / 180))
	j := reg((221.647 + 0.9025179 * d - 0.33 * math.Sin(v)) * (math.Pi / 180))
	a := reg((1.916 * math.Sin(eAnomaly) + 0.020 * math.Sin(2 * eAnomaly)) * math.Pi / 180)
	b := reg((5.552 * math.Sin(jAnomaly) + 0.167 * math.Sin(2 * jAnomaly)) * math.Pi / 180)
	k := reg(j + a - b)
	rvE := 1.00014 - 0.01672 * math.Cos(eAnomaly) - 0.00014 * math.Cos(2 * eAnomaly)
	psi := math.Asin(rvE / dist * math.Sin(k))

	ioAngle := reg((84.5506 + 203.4058630 * (d - dist / 173)) * (math.Pi / 180) + psi - b)

	return math.Mod(ioAngle * (180 / math.Pi) + 180, 360)
}

func reg(a float64) float64 {
	return math.Mod(a, 2 * math.Pi)
}

func source(meridian float64, ioDeg float64) string {
	var s string
	switch {
	case (meridian < 255 && meridian > 200) && (ioDeg < 250 && ioDeg > 220):
		s = "Io-A"
	case (meridian < 180 && meridian > 105) && (ioDeg < 100 && ioDeg > 80):
		s = "Io-B"
	case (meridian < 350 && meridian > 300) && (ioDeg < 250 && ioDeg > 230):
		s = "Io-C"
	}
	return s
}

func distance(eLon, eDistance, jLon, jDistance float64) float64 {
	angle := angleCalc(eLon, jLon)
	d2 := math.Pow(eDistance, 2) + math.Pow(jDistance, 2) - 2 * eDistance * jDistance * math.Cos(angle * (math.Pi / 180))
	d := math.Sqrt(d2)
	return d
}

func angleCalc(e, j float64) float64 {
	var a float64
	var b float64
	var angle float64
	if e < j {
		a = e
		b = j
	} else {
		b = e
		a = j
	}
	c1 := b - a
	c2 := 360 + a - b
	if c2 < c1 {
		angle = c2
	} else {
		angle = c1
	}
	return angle
}
<commit_msg>only display local time when lat/lon given<commit_after>// Originally inspired by:
// http://www.spaceacademy.net.au/spacelab/projects/jovrad/jovrad.htm,
// but extended and updated with some external libraries.
//
// Copyright 2016 Jeremy Bingham, under the MIT License.
// See the LICENSE file in this repository, or
// http://www.opensource.org/licenses/MIT

/*
jovian-noise is a program that will forecast possible upcoming Jupiter decameter radio storms, which you can hear on a shortwave radio (best between 18MHz and 23MHz), with a suitable receiver and antenna.

This program should be reasonably accurate, but there's always a possibility that the event will fail to materialize for one reason or another - possibly a bug with the program, possibly a bug with Jupiter itself.

This was originally inspired by a QBASIC program at http://www.spaceacademy.net.au/spacelab/projects/jovrad/jovrad.htm, but uses external libraries for many of the calculations and can optionally limit the returned results to when Jupiter will be above the horizon at your location.

To run this program, you will need to obtain the VSOP87 files for planet locations (an archive is located at ftp://cdsarc.u-strasbg.fr/pub/cats/VI%2F81/) and place them in a directory somewhere. The environment variable VSOP87 must be set to the path of the directory with the VSOP87 files.

    Usage of ./jovian-noise:
      -duration duration
       	    Duration (in golang ParseDuration format) from the start time to calculate the forecast (default 720h0m0s)
      -interval int
    	    Interval in minutes to calculate the forecast (default 30)
      -lat int
    	    Optional latitute. If given, will limit results to when Jupiter is above the horizon at this location. Requires -lon
      -lon int
    	    Optional longitude. If given, will limit results to when Jupiter is above the horizon at this location. Requires -lat
      -start-time string
    	    Start time (in RFC 3339 format) to calculate Jupiter radio storm forecasts (defaults to now)

Credits

Many web pages went into getting this together. The most immediately useful for this program were:

* http://www.spaceacademy.net.au/spacelab/projects/jovrad/jovrad.htm

* http://www.projectpluto.com/grs_form.htm

* https://github.com/akkana/scripts/blob/master/jsjupiter/jupiter.js

More Information about Jupiter amateur radio astronomy

* http://www.radiosky.com/rjcentral.html

* http://www.thrushobservatory.org/radio.htm

* http://radiojove.gsfc.nasa.gov/ -- NASA's Radio JOVE Project

License

Copyright 2016, Jeremy Bingham, under the terms of the MIT License.

*/
package main

import (
	"flag"
	"fmt"
	"log"
	"math"
	"os"
	"time"
	"github.com/soniakeys/meeus/elliptic"
	"github.com/soniakeys/meeus/sidereal"
	"github.com/soniakeys/meeus/globe"
	"github.com/soniakeys/meeus/rise"
	sexa "github.com/soniakeys/sexagesimal"
	"github.com/soniakeys/meeus/julian"
	pp "github.com/soniakeys/meeus/planetposition"
)

var months = []string{
	"",
	"Jan",
	"Feb",
	"Mar",
	"Apr",
	"May",
	"Jun",
	"Jul",
	"Aug",
	"Sep",
	"Oct",
	"Nov",
	"Dec",
}

const version string = "0.1.5"

func main() {
	startTime := flag.String("start-time", "", "Start time (in RFC 3339 format) to calculate Jupiter radio storm forecasts (defaults to now)")
	dur := flag.Duration("duration", 30 * 24 * time.Hour, "Duration (in golang ParseDuration format) from the start time to calculate the forecast")
	interval := flag.Int("interval", 30, "Interval in minutes to calculate the forecast")
	lat := flag.Int("lat", 0, "Optional latitute. If given, will limit results to when Jupiter is above the horizon at this location. Requires -lon")
	lon := flag.Int("lon", 0, "Optional longitude. If given, will limit results to when Jupiter is above the horizon at this location. Requires -lat")
	ver := flag.Bool("version", false, "Print version number and exit.")

	var t time.Time
	flag.Parse()

	if *ver {
		fmt.Printf("jovian-noise version %s\n", version)
		os.Exit(0)
	}

	if *startTime == "" {
		t = time.Now().UTC()
	} else {
		var err error
		t, err = time.Parse(time.RFC3339, *startTime)
		if err != nil {
			log.Println(err)
			os.Exit(1)
		}
		t = t.UTC()
	}
	if *lat != 0 && *lon == 0 || *lat == 0 && *lon != 0 {
		log.Println("Both -lat and -lon, or neither, must be supplied")
		os.Exit(1)
	}
	var risen bool
	var coords globe.Coord
	if *lat != 0 && *lat != 0 {
		coords.Lon = sexa.NewAngle(false, *lon, 0, 0).Rad()
		coords.Lat = sexa.NewAngle(false, *lat, 0, 0).Rad()
		risen = true
	}
	earth, err := pp.LoadPlanet(pp.Earth)
	if err != nil {
		panic(err)
	}
	jupiter, err := pp.LoadPlanet(pp.Jupiter)
	if err != nil {
		panic(err)
	}
	endTime := t.Add(*dur)
	fmt.Printf("################################################################################\n")
	fmt.Printf("\t\tJovian Decameter Radio Storm Forcast for:\n")
	fmt.Printf("\t\t    %s\n", t)
	fmt.Printf("\t\t\t\tuntil:\n\t\t    %s\n", endTime)

	var localHead, fmtStr string
	if *lat != 0 && *lon != 0 {
		lz, loff := t.Local().Zone()
		fmt.Printf("\t\t     Local time zone: %s (%05d)\n", lz, (loff / 60 / 60) * 100)
		fmt.Printf("\t\t   --- For coordinates %dº, %dº ---\n", *lat, *lon)
		localHead = " HH:MM (local) |"
		fmtStr = "%3d  %s %2d  %02d:%02d         %02d:%02d           %6.2f     %6.2f   %4.2f       %s\n"
	} else {
		fmtStr = "%3d  %s %2d  %02d:%02d         %6.2f     %6.2f   %4.2f       %s\n"
	}
	fmt.Printf("################################################################################\n")
	fmt.Printf("DY | Date  | HH:MM (UTC) |%s Io Phase | CML    | Dist(AU) | source\n", localHead)
	fmt.Printf("--------------------------------------------------------------------------------\n")

	for t.Before(endTime) {
		jd := julian.TimeToJD(t)
		var skip bool
		if risen {
			// round the day off
			rounded := t.Round(24 * time.Hour)
			if rounded.After(t) {
				rounded = rounded.Add(-24 * time.Hour)
			}
			secs := float64(t.Sub(rounded) / time.Second)
			ra, dec := elliptic.Position(jupiter, earth, jd)
			th0 := sidereal.Apparent0UT(jd)
			h0 := rise.Stdh0Stellar
			rising, _, set, err := rise.ApproxTimes(coords, h0, th0, ra, dec)
			if err != nil {
				log.Println(err)
				skip = true
			}
			if rising > secs || secs > set {
				// Jupiter's not out!
				//log.Printf("no jupiter: secs %f rising %f set %f", secs, rising, set )
				skip = true
			}
		}
		if !skip {
			el, _, eDist := earth.Position(jd)
			jl, _, jDist := jupiter.Position(jd)
			meridian := systemIIIMeridian(jd)
			eLon := el * (180 / math.Pi)
			jLon := jl * (180 / math.Pi)
			dist := distance(eLon, eDist, jLon, jDist)
			ioPhase := ioPos(jd, dist)
			rSource := source(meridian, ioPhase)

			if rSource != "" {
				if *lat != 0 && *lon != 0 {
					l := t.Local()
					fmt.Printf(fmtStr, t.YearDay(), months[t.Month()], t.Day(), t.Hour(), t.Minute(), l.Hour(), l.Minute(), ioPhase, meridian, dist, rSource)
				} else {
					fmt.Printf(fmtStr, t.YearDay(), months[t.Month()], t.Day(), t.Hour(), t.Minute(), ioPhase, meridian, dist, rSource)
				}
			}
		}
		t = t.Add(time.Duration(*interval) * time.Minute)
	}
}

// from http://www.projectpluto.com/grs_form.htm
func meridianCorrection(jd float64) float64 {
	jupMean := (jd - 2455636.938) * 360 / 4332.89709
	eqnCenter := 5.55 * math.Sin(jupMean)
	angle := (jd - 2451870.628) * 360 / 398.884 - eqnCenter
	correction := 11 * math.Sin(angle) + 5 * math.Cos(angle) - 1.25 * math.Cos(jupMean) - eqnCenter
	return correction
}

func systemIIIMeridian(jd float64) float64 {
	correction := meridianCorrection(jd)
	mFull := 138.41 + 870.4535567 * jd + correction
	m := math.Mod(mFull, 360)
	return m
}

func ioPos(jd float64, dist float64) float64 {
	// snagged the equations for this from
	// https://github.com/akkana/scripts/blob/master/jsjupiter/jupiter.js
	d := jd - 2415020
	v := reg(134.63 + 0.00111587 * d * (math.Pi / 180));
	eAnomaly := reg((358.476 + 0.9856003 * d) * (math.Pi / 180))
	jAnomaly := reg((225.328 + 0.0830853 * d + 0.33 * math.Sin(v)) * (math.Pi / 180))
	j := reg((221.647 + 0.9025179 * d - 0.33 * math.Sin(v)) * (math.Pi / 180))
	a := reg((1.916 * math.Sin(eAnomaly) + 0.020 * math.Sin(2 * eAnomaly)) * math.Pi / 180)
	b := reg((5.552 * math.Sin(jAnomaly) + 0.167 * math.Sin(2 * jAnomaly)) * math.Pi / 180)
	k := reg(j + a - b)
	rvE := 1.00014 - 0.01672 * math.Cos(eAnomaly) - 0.00014 * math.Cos(2 * eAnomaly)
	psi := math.Asin(rvE / dist * math.Sin(k))

	ioAngle := reg((84.5506 + 203.4058630 * (d - dist / 173)) * (math.Pi / 180) + psi - b)

	return math.Mod(ioAngle * (180 / math.Pi) + 180, 360)
}

func reg(a float64) float64 {
	return math.Mod(a, 2 * math.Pi)
}

func source(meridian float64, ioDeg float64) string {
	var s string
	switch {
	case (meridian < 255 && meridian > 200) && (ioDeg < 250 && ioDeg > 220):
		s = "Io-A"
	case (meridian < 180 && meridian > 105) && (ioDeg < 100 && ioDeg > 80):
		s = "Io-B"
	case (meridian < 350 && meridian > 300) && (ioDeg < 250 && ioDeg > 230):
		s = "Io-C"
	}
	return s
}

func distance(eLon, eDistance, jLon, jDistance float64) float64 {
	angle := angleCalc(eLon, jLon)
	d2 := math.Pow(eDistance, 2) + math.Pow(jDistance, 2) - 2 * eDistance * jDistance * math.Cos(angle * (math.Pi / 180))
	d := math.Sqrt(d2)
	return d
}

func angleCalc(e, j float64) float64 {
	var a float64
	var b float64
	var angle float64
	if e < j {
		a = e
		b = j
	} else {
		b = e
		a = j
	}
	c1 := b - a
	c2 := 360 + a - b
	if c2 < c1 {
		angle = c2
	} else {
		angle = c1
	}
	return angle
}
<|endoftext|>


<commit_before>package lib

import (
	"github.com/mitsuse/pushbullet-go"
	"github.com/mitsuse/pushbullet-go/requests"
	"github.com/op/go-logging"
	"github.com/tucnak/telebot"
	"gopkg.in/gomail.v2"
	"strings"
	"time"
)

// Sends a Pushbullet-Push containing the given data to the saved API-key.
func sendPush(apiKey string, name string, url string, newStatus string, oldStatus string) {
	logging.MustGetLogger("").Debug("Sending Push about \"" + url + "\"...")

	pb := pushbullet.New(apiKey)

	push := requests.NewLink()
	push.Title = GetConfiguration().Dynamic.Title + " - Status Change"
	push.Body = name + " went from \"" + oldStatus + "\" to \"" + newStatus + "\"."
	push.Url = url

	_, err := pb.PostPushesLink(push)
	if err != nil {
		logging.MustGetLogger("").Error("Unable to send Push: ", err)
	}
}

// Sends an email containing the given data to the saved e-mail-address.
// Needs a configured SMTP-server (in config-file).
func sendMail(recipient string, name string, url string, newStatus string, oldStatus string) {
	if GetConfiguration().Mailer.Host == "" || GetConfiguration().Mailer.Host == "smtp.mymail.com" {
		logging.MustGetLogger("").Warning("Not sending email because of missing configuration.")
		return
	}

	logging.MustGetLogger("").Debug("Sending email about \"" + url + "\"...")

	mConf := GetConfiguration().Mailer

	m := gomail.NewMessage()
	m.SetAddressHeader("From", mConf.From, GetConfiguration().Dynamic.Title)
	m.SetHeader("To", recipient)
	m.SetHeader("Subject", "Status Change: "+name)
	m.SetBody("text/html", "Hello,

"+
		""+name+""+" ("+url+") went on "+time.Now().Format("02.01.2006")+" at "+time.Now().Format("15:04:05")+" from \""+oldStatus+"\" to \""+newStatus+"\".

"+
		"Sincerely,
"+GetConfiguration().Dynamic.Title+"

"+
		"This email was sent automatically, please do not respond to it.")

	d := gomail.NewPlainDialer(mConf.Host, mConf.Port, mConf.User, mConf.Password)

	if err := d.DialAndSend(m); err != nil {
		logging.MustGetLogger("").Error("Unable to send email: ", err)
	}
}

func sendTelegramMessage(userId int, name string, url string, newStatus string, oldStatus string) {
	if GetConfiguration().TelegramBotApiKey == "" {
		logging.MustGetLogger("").Warning("Not sending Telegram-message because of missing configuration.")
		return
	}

	logging.MustGetLogger("").Debug("Sending Telegram-message about \"" + url + "\"...")

	statusEmoji := "\U00002757"
	if strings.HasPrefix(newStatus, "2") {
		statusEmoji = "\U00002714"
	} else if strings.HasPrefix(newStatus, "3") {
		statusEmoji = "\U000026A0"
	} else if strings.HasPrefix(newStatus, "4") || strings.HasPrefix(newStatus, "5") {
		statusEmoji = "\U0000274C"
	}

	Bot.SendMessage(telebot.User{ID: userId}, "*Status Change: "+statusEmoji+" "+name+"*\n`"+url+"` went from `"+oldStatus+"` to `"+newStatus+"`.", &SendOptions)
}
<commit_msg>Update Telegram-Notification style<commit_after>package lib

import (
	"github.com/mitsuse/pushbullet-go"
	"github.com/mitsuse/pushbullet-go/requests"
	"github.com/op/go-logging"
	"github.com/tucnak/telebot"
	"gopkg.in/gomail.v2"
	"strings"
	"time"
)

// Sends a Pushbullet-Push containing the given data to the saved API-key.
func sendPush(apiKey string, name string, url string, newStatus string, oldStatus string) {
	logging.MustGetLogger("").Debug("Sending Push about \"" + url + "\"...")

	pb := pushbullet.New(apiKey)

	push := requests.NewLink()
	push.Title = GetConfiguration().Dynamic.Title + " - Status Change"
	push.Body = name + " went from \"" + oldStatus + "\" to \"" + newStatus + "\"."
	push.Url = url

	_, err := pb.PostPushesLink(push)
	if err != nil {
		logging.MustGetLogger("").Error("Unable to send Push: ", err)
	}
}

// Sends an email containing the given data to the saved e-mail-address.
// Needs a configured SMTP-server (in config-file).
func sendMail(recipient string, name string, url string, newStatus string, oldStatus string) {
	if GetConfiguration().Mailer.Host == "" || GetConfiguration().Mailer.Host == "smtp.mymail.com" {
		logging.MustGetLogger("").Warning("Not sending email because of missing configuration.")
		return
	}

	logging.MustGetLogger("").Debug("Sending email about \"" + url + "\"...")

	mConf := GetConfiguration().Mailer

	m := gomail.NewMessage()
	m.SetAddressHeader("From", mConf.From, GetConfiguration().Dynamic.Title)
	m.SetHeader("To", recipient)
	m.SetHeader("Subject", "Status Change: "+name)
	m.SetBody("text/html", "Hello,

"+
		""+name+""+" ("+url+") went on "+time.Now().Format("02.01.2006")+" at "+time.Now().Format("15:04:05")+" from \""+oldStatus+"\" to \""+newStatus+"\".

"+
		"Sincerely,
"+GetConfiguration().Dynamic.Title+"

"+
		"This email was sent automatically, please do not respond to it.")

	d := gomail.NewPlainDialer(mConf.Host, mConf.Port, mConf.User, mConf.Password)

	if err := d.DialAndSend(m); err != nil {
		logging.MustGetLogger("").Error("Unable to send email: ", err)
	}
}

func sendTelegramMessage(userId int, name string, url string, newStatus string, oldStatus string) {
	if GetConfiguration().TelegramBotApiKey == "" {
		logging.MustGetLogger("").Warning("Not sending Telegram-message because of missing configuration.")
		return
	}

	logging.MustGetLogger("").Debug("Sending Telegram-message about \"" + url + "\"...")

	statusEmoji := "\U00002757"
	if strings.HasPrefix(newStatus, "2") {
		statusEmoji = "\U00002714"
	} else if strings.HasPrefix(newStatus, "3") {
		statusEmoji = "\U000026A0"
	} else if strings.HasPrefix(newStatus, "4") || strings.HasPrefix(newStatus, "5") {
		statusEmoji = "\U0000274C"
	}

	Bot.SendMessage(telebot.User{ID: userId}, "*Status Change: "+name+"* "+statusEmoji+"\n`"+url+"` went from `"+oldStatus+"` to `"+newStatus+"`.", &SendOptions)
}
<|endoftext|>


<commit_before>// Copyright 2015 CloudAwan LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package application

import (
	"encoding/json"
	"errors"
	"github.com/cloudawan/cloudone/control"
	"github.com/cloudawan/cloudone/utility/lock"
	"github.com/cloudawan/cloudone_utility/deepcopy"
	"github.com/cloudawan/cloudone_utility/random"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
)

const (
	LockKind = "cluster_application"
)

type Cluster struct {
	Name                      string
	Description               string
	ReplicationControllerJson string
	ServiceJson               string
	Environment               map[string]string
	ScriptType                string
	ScriptContent             string
}

func getLockName(namespace string, name string) string {
	return namespace + "." + name
}

func LaunchClusterApplication(kubeapiHost string, kubeapiPort int, namespace string, name string, environmentSlice []interface{}, size int, replicationControllerExtraJsonMap map[string]interface{}) error {
	if lock.AcquireLock(LockKind, getLockName(namespace, name), 0) == false {
		return errors.New("Template is under deployment")
	}

	defer lock.ReleaseLock(LockKind, getLockName(namespace, name))

	cluster, err := GetStorage().LoadClusterApplication(name)
	if err != nil {
		log.Error("Load cluster application error %s", err)
		return err
	}

	switch cluster.ScriptType {
	case "none":
		return LaunchClusterApplicationNoScript(kubeapiHost, kubeapiPort, namespace, cluster, environmentSlice, size, replicationControllerExtraJsonMap)
	case "python":
		return LaunchClusterApplicationPython(kubeapiHost, kubeapiPort, namespace, cluster, environmentSlice, size, replicationControllerExtraJsonMap)
	default:
		log.Error("No such script type: %s", cluster.ScriptType)
		return errors.New("No such script type: " + cluster.ScriptType)
	}
}

func LaunchClusterApplicationNoScript(kubeapiHost string, kubeapiPort int, namespace string, cluster *Cluster, environmentSlice []interface{}, size int, replicationControllerExtraJsonMap map[string]interface{}) error {
	replicationControllerJsonMap := make(map[string]interface{})
	err := json.Unmarshal([]byte(cluster.ReplicationControllerJson), &replicationControllerJsonMap)
	if err != nil {
		log.Error("Unmarshal replication controller json for cluster application error %s", err)
		return err
	}

	serviceJsonMap := make(map[string]interface{})
	err = json.Unmarshal([]byte(cluster.ServiceJson), &serviceJsonMap)
	if err != nil {
		log.Error("Unmarshal service json for cluster application error %s", err)
		return err
	}

	// Configure extra json body
	// It is used for user to input any configuration
	if replicationControllerExtraJsonMap != nil {
		deepcopy.DeepOverwriteJsonMap(replicationControllerExtraJsonMap, replicationControllerJsonMap)
	}

	// Add environment variable
	if environmentSlice != nil {
		specJsonMap, ok := replicationControllerJsonMap["spec"].(map[string]interface{})
		if ok {
			templateJsonMap, ok := specJsonMap["template"].(map[string]interface{})
			if ok {
				specJsonMap, ok := templateJsonMap["spec"].(map[string]interface{})
				if ok {
					containerSlice, ok := specJsonMap["containers"].([]interface{})
					if ok {
						for i := 0; i < len(containerSlice); i++ {
							_, ok := containerSlice[i].(map[string]interface{})["env"].([]interface{})
							if ok {
								for _, environment := range environmentSlice {
									containerSlice[i].(map[string]interface{})["env"] = append(containerSlice[i].(map[string]interface{})["env"].([]interface{}), environment)
								}
							} else {
								containerSlice[i].(map[string]interface{})["env"] = environmentSlice
							}
						}
					}
				}
			}
		}
	}

	// Change size
	specJsonMap, ok := replicationControllerJsonMap["spec"].(map[string]interface{})
	if ok {
		specJsonMap["replicas"] = size
	}

	err = control.CreateReplicationControllerWithJson(kubeapiHost, kubeapiPort, namespace, replicationControllerJsonMap)
	if err != nil {
		log.Error("CreateReplicationControllerWithJson error %s", err)
		return err
	}
	err = control.CreateServiceWithJson(kubeapiHost, kubeapiPort, namespace, serviceJsonMap)
	if err != nil {
		log.Error("CreateReplicationControllerWithJson error %s", err)
		return err
	}

	return nil
}

func LaunchClusterApplicationPython(kubeapiHost string, kubeapiPort int, namespace string, cluster *Cluster, environmentSlice []interface{}, size int, replicationControllerExtraJsonMap map[string]interface{}) error {
	replicationControllerJsonMap := make(map[string]interface{})
	err := json.Unmarshal([]byte(cluster.ReplicationControllerJson), &replicationControllerJsonMap)
	if err != nil {
		log.Error("Unmarshal replication controller json for cluster application error %s", err)
		return err
	}

	// Configure extra json body
	// It is used for user to input any configuration
	if replicationControllerExtraJsonMap != nil {
		deepcopy.DeepOverwriteJsonMap(replicationControllerExtraJsonMap, replicationControllerJsonMap)
	}

	// Add environment variable
	if environmentSlice != nil {
		specJsonMap, ok := replicationControllerJsonMap["spec"].(map[string]interface{})
		if ok {
			templateJsonMap, ok := specJsonMap["template"].(map[string]interface{})
			if ok {
				specJsonMap, ok := templateJsonMap["spec"].(map[string]interface{})
				if ok {
					containerSlice, ok := specJsonMap["containers"].([]interface{})
					if ok {
						for i := 0; i < len(containerSlice); i++ {
							_, ok := containerSlice[i].(map[string]interface{})["env"].([]interface{})
							if ok {
								for _, environment := range environmentSlice {
									containerSlice[i].(map[string]interface{})["env"] = append(containerSlice[i].(map[string]interface{})["env"].([]interface{}), environment)
								}
							} else {
								containerSlice[i].(map[string]interface{})["env"] = environmentSlice
							}
						}
					}
				}
			}
		}
	}

	replicationControllerByteSlice, err := json.Marshal(replicationControllerJsonMap)
	if err != nil {
		log.Error("Marshal replication controller json for cluster application error %s", err)
		return err
	}

	environmentByteSlice, err := json.Marshal(environmentSlice)
	if err != nil {
		log.Error("Marshal environment json for cluster application error %s", err)
		return err
	}

	// Generate random work space
	workingDirectory := "/tmp/tmp_" + random.UUID()
	replicationControllerFileName := "replication-controller.json"
	serviceFileName := "service.json"
	scriptFileName := "script"
	environmentFileName := "environment.json"

	// Check working space
	if _, err := os.Stat(workingDirectory); os.IsNotExist(err) {
		err := os.MkdirAll(workingDirectory, os.ModePerm)
		if err != nil {
			log.Error("Create non-existing directory %s error: %s", workingDirectory, err)
			return err
		}
	}

	err = ioutil.WriteFile(workingDirectory+string(os.PathSeparator)+environmentFileName, environmentByteSlice, os.ModePerm)
	if err != nil {
		log.Error("Write environment json file for cluster application error %s", err)
		return err
	}

	err = ioutil.WriteFile(workingDirectory+string(os.PathSeparator)+replicationControllerFileName, replicationControllerByteSlice, os.ModePerm)
	if err != nil {
		log.Error("Write replication controller json file for cluster application error %s", err)
		return err
	}

	err = ioutil.WriteFile(workingDirectory+string(os.PathSeparator)+serviceFileName, []byte(cluster.ServiceJson), os.ModePerm)
	if err != nil {
		log.Error("Write service json file for cluster application error %s", err)
		return err
	}

	err = ioutil.WriteFile(workingDirectory+string(os.PathSeparator)+scriptFileName, []byte(cluster.ScriptContent), os.ModePerm)
	if err != nil {
		log.Error("Write script file for cluster application error %s", err)
		return err
	}

	command := exec.Command("python", scriptFileName,
		"--application_name="+cluster.Name,
		"--kubeapi_host_and_port=http://"+kubeapiHost+":"+strconv.Itoa(kubeapiPort),
		"--namespace="+namespace,
		"--size="+strconv.Itoa(size),
		"--service_file_name="+serviceFileName,
		"--replication_controller_file_name="+replicationControllerFileName,
		"--environment_file_name="+environmentFileName,
		"--timeout_in_second=120",
		"--action=create")
	command.Dir = workingDirectory
	out, err := command.CombinedOutput()
	log.Debug(string(out))
	if err != nil {
		log.Error("Run python script file for cluster application error %s", err)
		return errors.New("Error: " + err.Error() + " Output: " + string(out))
	}

	// Remove working space
	err = os.RemoveAll(workingDirectory)
	if err != nil {
		log.Error("Remove the working directory for cluster application error %s", err)
		return err
	}

	return nil
}
<commit_msg>Overwrite environment rather than appending in case the environment value is already in the replication controller file<commit_after>// Copyright 2015 CloudAwan LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package application

import (
	"encoding/json"
	"errors"
	"github.com/cloudawan/cloudone/control"
	"github.com/cloudawan/cloudone/utility/lock"
	"github.com/cloudawan/cloudone_utility/deepcopy"
	"github.com/cloudawan/cloudone_utility/random"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
)

const (
	LockKind = "cluster_application"
)

type Cluster struct {
	Name                      string
	Description               string
	ReplicationControllerJson string
	ServiceJson               string
	Environment               map[string]string
	ScriptType                string
	ScriptContent             string
}

func getLockName(namespace string, name string) string {
	return namespace + "." + name
}

func LaunchClusterApplication(kubeapiHost string, kubeapiPort int, namespace string, name string, environmentSlice []interface{}, size int, replicationControllerExtraJsonMap map[string]interface{}) error {
	if lock.AcquireLock(LockKind, getLockName(namespace, name), 0) == false {
		return errors.New("Template is under deployment")
	}

	defer lock.ReleaseLock(LockKind, getLockName(namespace, name))

	cluster, err := GetStorage().LoadClusterApplication(name)
	if err != nil {
		log.Error("Load cluster application error %s", err)
		return err
	}

	switch cluster.ScriptType {
	case "none":
		return LaunchClusterApplicationNoScript(kubeapiHost, kubeapiPort, namespace, cluster, environmentSlice, size, replicationControllerExtraJsonMap)
	case "python":
		return LaunchClusterApplicationPython(kubeapiHost, kubeapiPort, namespace, cluster, environmentSlice, size, replicationControllerExtraJsonMap)
	default:
		log.Error("No such script type: %s", cluster.ScriptType)
		return errors.New("No such script type: " + cluster.ScriptType)
	}
}

func LaunchClusterApplicationNoScript(kubeapiHost string, kubeapiPort int, namespace string, cluster *Cluster, environmentSlice []interface{}, size int, replicationControllerExtraJsonMap map[string]interface{}) error {
	replicationControllerJsonMap := make(map[string]interface{})
	err := json.Unmarshal([]byte(cluster.ReplicationControllerJson), &replicationControllerJsonMap)
	if err != nil {
		log.Error("Unmarshal replication controller json for cluster application error %s", err)
		return err
	}

	serviceJsonMap := make(map[string]interface{})
	err = json.Unmarshal([]byte(cluster.ServiceJson), &serviceJsonMap)
	if err != nil {
		log.Error("Unmarshal service json for cluster application error %s", err)
		return err
	}

	// Configure extra json body
	// It is used for user to input any configuration
	if replicationControllerExtraJsonMap != nil {
		deepcopy.DeepOverwriteJsonMap(replicationControllerExtraJsonMap, replicationControllerJsonMap)
	}

	// Add environment variable
	if environmentSlice != nil {
		specJsonMap, ok := replicationControllerJsonMap["spec"].(map[string]interface{})
		if ok {
			templateJsonMap, ok := specJsonMap["template"].(map[string]interface{})
			if ok {
				specJsonMap, ok := templateJsonMap["spec"].(map[string]interface{})
				if ok {
					containerSlice, ok := specJsonMap["containers"].([]interface{})
					if ok {
						for i := 0; i < len(containerSlice); i++ {
							oldEnvironmentSlice, ok := containerSlice[i].(map[string]interface{})["env"].([]interface{})
							if ok {
								// Track old environment
								environmentMap := make(map[string]interface{})
								for _, oldEnvironment := range oldEnvironmentSlice {
									oldEnvironmentJsonMap, _ := oldEnvironment.(map[string]interface{})
									name, _ := oldEnvironmentJsonMap["name"].(string)
									environmentMap[name] = oldEnvironment
								}

								// Add or overwrite with new value
								for _, environment := range environmentSlice {
									environmentJsonMap, _ := environment.(map[string]interface{})
									name, _ := environmentJsonMap["name"].(string)
									environmentMap[name] = environmentJsonMap
								}

								// Use new slice which the user input overwrite the old data
								newEnvironmentSlice := make([]interface{}, 0)
								for _, value := range environmentMap {
									newEnvironmentSlice = append(newEnvironmentSlice, value)
								}
								containerSlice[i].(map[string]interface{})["env"] = newEnvironmentSlice
							} else {
								containerSlice[i].(map[string]interface{})["env"] = environmentSlice
							}
						}
					}
				}
			}
		}
	}

	// Change size
	specJsonMap, ok := replicationControllerJsonMap["spec"].(map[string]interface{})
	if ok {
		specJsonMap["replicas"] = size
	}

	err = control.CreateReplicationControllerWithJson(kubeapiHost, kubeapiPort, namespace, replicationControllerJsonMap)
	if err != nil {
		log.Error("CreateReplicationControllerWithJson error %s", err)
		return err
	}
	err = control.CreateServiceWithJson(kubeapiHost, kubeapiPort, namespace, serviceJsonMap)
	if err != nil {
		log.Error("CreateReplicationControllerWithJson error %s", err)
		return err
	}

	return nil
}

func LaunchClusterApplicationPython(kubeapiHost string, kubeapiPort int, namespace string, cluster *Cluster, environmentSlice []interface{}, size int, replicationControllerExtraJsonMap map[string]interface{}) error {
	replicationControllerJsonMap := make(map[string]interface{})
	err := json.Unmarshal([]byte(cluster.ReplicationControllerJson), &replicationControllerJsonMap)
	if err != nil {
		log.Error("Unmarshal replication controller json for cluster application error %s", err)
		return err
	}

	// Configure extra json body
	// It is used for user to input any configuration
	if replicationControllerExtraJsonMap != nil {
		deepcopy.DeepOverwriteJsonMap(replicationControllerExtraJsonMap, replicationControllerJsonMap)
	}

	// Add environment variable
	if environmentSlice != nil {
		specJsonMap, ok := replicationControllerJsonMap["spec"].(map[string]interface{})
		if ok {
			templateJsonMap, ok := specJsonMap["template"].(map[string]interface{})
			if ok {
				specJsonMap, ok := templateJsonMap["spec"].(map[string]interface{})
				if ok {
					containerSlice, ok := specJsonMap["containers"].([]interface{})
					if ok {
						for i := 0; i < len(containerSlice); i++ {
							oldEnvironmentSlice, ok := containerSlice[i].(map[string]interface{})["env"].([]interface{})
							if ok {
								// Track old environment
								environmentMap := make(map[string]interface{})
								for _, oldEnvironment := range oldEnvironmentSlice {
									oldEnvironmentJsonMap, _ := oldEnvironment.(map[string]interface{})
									name, _ := oldEnvironmentJsonMap["name"].(string)
									environmentMap[name] = oldEnvironment
								}

								// Add or overwrite with new value
								for _, environment := range environmentSlice {
									environmentJsonMap, _ := environment.(map[string]interface{})
									name, _ := environmentJsonMap["name"].(string)
									environmentMap[name] = environment
								}

								// Use new slice which the user input overwrite the old data
								newEnvironmentSlice := make([]interface{}, 0)
								for _, value := range environmentMap {
									newEnvironmentSlice = append(newEnvironmentSlice, value)
								}
								containerSlice[i].(map[string]interface{})["env"] = newEnvironmentSlice
							} else {
								containerSlice[i].(map[string]interface{})["env"] = environmentSlice
							}
						}
					}
				}
			}
		}
	}

	replicationControllerByteSlice, err := json.Marshal(replicationControllerJsonMap)
	if err != nil {
		log.Error("Marshal replication controller json for cluster application error %s", err)
		return err
	}

	environmentByteSlice, err := json.Marshal(environmentSlice)
	if err != nil {
		log.Error("Marshal environment json for cluster application error %s", err)
		return err
	}

	// Generate random work space
	workingDirectory := "/tmp/tmp_" + random.UUID()
	replicationControllerFileName := "replication-controller.json"
	serviceFileName := "service.json"
	scriptFileName := "script"
	environmentFileName := "environment.json"

	// Check working space
	if _, err := os.Stat(workingDirectory); os.IsNotExist(err) {
		err := os.MkdirAll(workingDirectory, os.ModePerm)
		if err != nil {
			log.Error("Create non-existing directory %s error: %s", workingDirectory, err)
			return err
		}
	}

	err = ioutil.WriteFile(workingDirectory+string(os.PathSeparator)+environmentFileName, environmentByteSlice, os.ModePerm)
	if err != nil {
		log.Error("Write environment json file for cluster application error %s", err)
		return err
	}

	err = ioutil.WriteFile(workingDirectory+string(os.PathSeparator)+replicationControllerFileName, replicationControllerByteSlice, os.ModePerm)
	if err != nil {
		log.Error("Write replication controller json file for cluster application error %s", err)
		return err
	}

	err = ioutil.WriteFile(workingDirectory+string(os.PathSeparator)+serviceFileName, []byte(cluster.ServiceJson), os.ModePerm)
	if err != nil {
		log.Error("Write service json file for cluster application error %s", err)
		return err
	}

	err = ioutil.WriteFile(workingDirectory+string(os.PathSeparator)+scriptFileName, []byte(cluster.ScriptContent), os.ModePerm)
	if err != nil {
		log.Error("Write script file for cluster application error %s", err)
		return err
	}

	command := exec.Command("python", scriptFileName,
		"--application_name="+cluster.Name,
		"--kubeapi_host_and_port=http://"+kubeapiHost+":"+strconv.Itoa(kubeapiPort),
		"--namespace="+namespace,
		"--size="+strconv.Itoa(size),
		"--service_file_name="+serviceFileName,
		"--replication_controller_file_name="+replicationControllerFileName,
		"--environment_file_name="+environmentFileName,
		"--timeout_in_second=120",
		"--action=create")
	command.Dir = workingDirectory
	out, err := command.CombinedOutput()
	log.Debug(string(out))
	if err != nil {
		log.Error("Run python script file for cluster application error %s", err)
		return errors.New("Error: " + err.Error() + " Output: " + string(out))
	}

	// Remove working space
	err = os.RemoveAll(workingDirectory)
	if err != nil {
		log.Error("Remove the working directory for cluster application error %s", err)
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package apps

import (
	"encoding/json"
	"fmt"
	"strings"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/app_helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/random_name"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1&results-per-page=150", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = random_name.CATSRandomName("APP")
	})

	AfterEach(func() {
		app_helpers.AppReport(appName, DEFAULT_TIMEOUT)

		Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		Describe("Context path", func() {
			var app2 string
			var path = "/imposter_dora"

			BeforeEach(func() {
				Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(appName)

				Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				app2 = random_name.CATSRandomName("APP")
				Expect(cf.Cf("push", app2, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().HelloWorld, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(app2)
				Expect(cf.Cf("start", app2).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			})

			AfterEach(func() {
				Expect(cf.Cf("delete", app2, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes another app available via same host and domain, but different path", func() {
				getRoutePath := fmt.Sprintf("/v2/routes?q=host:%s", appName)
				routeBody := cf.Cf("curl", getRoutePath).Wait(DEFAULT_TIMEOUT).Out.Contents()
				var routeJSON struct {
					Resources []struct {
						Entity struct {
							SpaceGuid  string `json:"space_guid"`
							DomainGuid string `json:"domain_guid"`
						} `json:"entity"`
					} `json:"resources"`
				}
				json.Unmarshal([]byte(routeBody), &routeJSON)

				spaceGuid := routeJSON.Resources[0].Entity.SpaceGuid
				domainGuid := routeJSON.Resources[0].Entity.DomainGuid
				appGuid := cf.Cf("app", app2, "--guid").Wait(DEFAULT_TIMEOUT).Out.Contents()

				jsonBody := "{\"host\":\"" + appName + "\", \"path\":\"" + path + "\", \"domain_guid\":\"" + domainGuid + "\",\"space_guid\":\"" + spaceGuid + "\"}"
				routePostResponseBody := cf.Cf("curl", "/v2/routes", "-X", "POST", "-d", jsonBody).Wait(CF_PUSH_TIMEOUT).Out.Contents()

				var routeResponseJSON struct {
					Metadata struct {
						Guid string `json:"guid"`
					} `json:"metadata"`
				}
				json.Unmarshal([]byte(routePostResponseBody), &routeResponseJSON)
				routeGuid := routeResponseJSON.Metadata.Guid

				Expect(cf.Cf("curl", "/v2/apps/"+strings.TrimSpace(string(appGuid))+"/routes/"+string(routeGuid), "-X", "PUT").Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

				Eventually(func() string {
					return helpers.CurlApp(appName, path)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
			})
		})

		Context("multiple instances", func() {
			BeforeEach(func() {
				Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(appName)
				Expect(cf.Cf("scale", appName, "-i", "2").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("is able to start all instances", func() {
				Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				Eventually(func() *Session {
					return cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
				}, DEFAULT_TIMEOUT).Should(Say("#0   running"))

				Eventually(func() *Session {
					return cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
				}, DEFAULT_TIMEOUT).Should(Say("#1   running"))
			})
		})

		It("makes system environment variables available", func() {
			Expect(cf.Cf("push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"\[(\{\\"external\\":[0-9]+,\\"internal\\":[0-9]+\},?)+\]"`))
		})

		It("generates an app usage 'started' event", func() {
			Expect(cf.Cf(
				"push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT),
			).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			It("makes the app reachable again", func() {
				Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().HelloWorld, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)
			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<commit_msg>Fix race condition when stopping and starting an app.<commit_after>package apps

import (
	"encoding/json"
	"fmt"
	"strings"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/app_helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/random_name"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1&results-per-page=150", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = random_name.CATSRandomName("APP")
	})

	AfterEach(func() {
		app_helpers.AppReport(appName, DEFAULT_TIMEOUT)

		Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		Describe("Context path", func() {
			var app2 string
			var path = "/imposter_dora"

			BeforeEach(func() {
				Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(appName)

				Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				app2 = random_name.CATSRandomName("APP")
				Expect(cf.Cf("push", app2, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().HelloWorld, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(app2)
				Expect(cf.Cf("start", app2).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			})

			AfterEach(func() {
				Expect(cf.Cf("delete", app2, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes another app available via same host and domain, but different path", func() {
				getRoutePath := fmt.Sprintf("/v2/routes?q=host:%s", appName)
				routeBody := cf.Cf("curl", getRoutePath).Wait(DEFAULT_TIMEOUT).Out.Contents()
				var routeJSON struct {
					Resources []struct {
						Entity struct {
							SpaceGuid  string `json:"space_guid"`
							DomainGuid string `json:"domain_guid"`
						} `json:"entity"`
					} `json:"resources"`
				}
				json.Unmarshal([]byte(routeBody), &routeJSON)

				spaceGuid := routeJSON.Resources[0].Entity.SpaceGuid
				domainGuid := routeJSON.Resources[0].Entity.DomainGuid
				appGuid := cf.Cf("app", app2, "--guid").Wait(DEFAULT_TIMEOUT).Out.Contents()

				jsonBody := "{\"host\":\"" + appName + "\", \"path\":\"" + path + "\", \"domain_guid\":\"" + domainGuid + "\",\"space_guid\":\"" + spaceGuid + "\"}"
				routePostResponseBody := cf.Cf("curl", "/v2/routes", "-X", "POST", "-d", jsonBody).Wait(CF_PUSH_TIMEOUT).Out.Contents()

				var routeResponseJSON struct {
					Metadata struct {
						Guid string `json:"guid"`
					} `json:"metadata"`
				}
				json.Unmarshal([]byte(routePostResponseBody), &routeResponseJSON)
				routeGuid := routeResponseJSON.Metadata.Guid

				Expect(cf.Cf("curl", "/v2/apps/"+strings.TrimSpace(string(appGuid))+"/routes/"+string(routeGuid), "-X", "PUT").Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

				Eventually(func() string {
					return helpers.CurlApp(appName, path)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
			})
		})

		Context("multiple instances", func() {
			BeforeEach(func() {
				Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(appName)
				Expect(cf.Cf("scale", appName, "-i", "2").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("is able to start all instances", func() {
				Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				Eventually(func() *Session {
					return cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
				}, DEFAULT_TIMEOUT).Should(Say("#0   running"))

				Eventually(func() *Session {
					return cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
				}, DEFAULT_TIMEOUT).Should(Say("#1   running"))
			})
		})

		It("makes system environment variables available", func() {
			Expect(cf.Cf("push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"\[(\{\\"external\\":[0-9]+,\\"internal\\":[0-9]+\},?)+\]"`))
		})

		It("generates an app usage 'started' event", func() {
			Expect(cf.Cf(
				"push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT),
			).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			It("makes the app reachable again", func() {
				Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

				Eventually(func() bool {
					found, _ := lastAppUsageEvent(appName, "STOPPED")
					return found
				}, DEFAULT_TIMEOUT).Should(BeTrue())

				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().HelloWorld, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)
			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<|endoftext|>


<commit_before>package assembler

import (
	"context"

	"go.polydawn.net/go-timeless-api"
	"go.polydawn.net/go-timeless-api/rio"
	"go.polydawn.net/rio/fs"
)

type Assembler struct {
	targetBase     fs.AbsolutePath
	cacheDir       fs.AbsolutePath
	unpackTool     rio.UnpackFunc
	placerTool     func( /*todo*/ )
	fillerDirProps fs.Metadata
}

/*
	Unpacking with an assembler proxies the unpack command,
	but transforms the unpack path into one relative to the assembler's configured base path,
	then runs the unpack in its own goroutine,
	performs the unpack into a CAS cache area,
	and finally uses a 'placer' to get the now-cached content
	to appear in the (translated) target path.

	Since the work is done in parallel, the wareID returned is always zero,
	and any errors will be usage and args errors;
	the caller must check the error returned by `Wait`,
	which will return any other gathered errors.

	If the unpack path is deep, the assembler will create parent dirs as necessary.
	TODO finish defining ordering behavior and mounts and such.
*/
func (a *Assembler) Unpack(
	ctx context.Context, // Long-running call.  Cancellable.
	wareID api.WareID, // What wareID to fetch for unpacking.
	path string, // Where to unpack the fileset (absolute path).
	filters api.FilesetFilters, // Optionally: filters we should apply while unpacking.
	warehouses []api.WarehouseAddr, // Warehouses we can try to fetch from.
	monitor rio.Monitor, // Optionally: callbacks for progress monitoring.
) (api.WareID, error) {
	return api.WareID{}, nil
}

func (a *Assembler) Wait() error {
	return nil
}
<commit_msg>assembler: a port of the repeatr AssemblerPart struct.<commit_after>package assembler

import (
	"go.polydawn.net/rio/fs"
)

type AssemblyPart struct {
	// Path to use as source for mounting or copy.
	//
	// When assembly is invoked, a source root is required; this
	// path will be relative to that.
	// (Typically the source root with be the CAS cache storage area
	// when this is used by repeatr to launch containers.)
	SourcePath fs.RelPath

	// Path to fill via mounting or copy.
	//
	// When assembly is invoked, a target root is required; this
	// path will be relative to that.
	// (Typically the target root is the path a container will be chroot'd
	// into when this is used by repeatr to launch containers.)
	TargetPath fs.RelPath

	// Toggles whether or not any mounts should be writable.
	// Default is false/read-only.
	//
	// If using a "copy" placer, this will be *ignored*: the paths
	// WILL be writable.  Do not use copy placers if this is an
	// issue; they do not have the power to respect this property.
	//
	// If `Writable && BareMount` are both true, then
	// *this will allow modifications to the source path*.
	// (This is used by repeatr to allow mounts through to the host.
	//
	// TODO REVIEW: host mounts require that the source root not be the
	// CAS cache root path :/  maybe we shoud allow a root per entry...?
	//
	// TODO REVIEW: the behavior for copy placers.  I'd rather they errored loudly.
	// It would be a change from historic repeatr, but probably a worthy one.
	Writable bool

	// BareMount requests direct passthrough -- what this means varies based on writability:
	// if writable==false, this means continuing changes in sourcepath are visible realtime;
	// if writable==true, the placer will employ a bind mount *without* COW or isolation,
	// **meaning mutations will be applied to the sourcepath**.
	BareMount bool
}

// TODO REVIEW: overall, above: yeah, maybe we need some more usage-specific enums,
// rather than this hash of bools.

//type Assembler struct {
//	targetBase     fs.AbsolutePath
//	cacheDir       fs.AbsolutePath
//	unpackTool     rio.UnpackFunc
//	placerTool     func( /*todo*/ )
//	fillerDirProps fs.Metadata
//}
<|endoftext|>


<commit_before>package hekalocal

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

	"code.google.com/p/go-uuid/uuid"

	"github.com/hhkbp2/go-strftime"

	"github.com/mozilla-services/heka/message"
	"github.com/mozilla-services/heka/pipeline"
	"github.com/mozilla-services/heka/plugins/elasticsearch"
)

// JSONEncoder serializes messages to JSON.
type JSONEncoder struct {
	config *JSONEncoderConfig
	coord  *elasticsearch.ElasticSearchCoordinates
}

type fieldEncoder func(map[string]interface{}, *message.Message)

// JSONEncoderConfig contains the optional field names to put Message fields into.
type JSONEncoderConfig struct {
	TimestampField  string `toml:"timestamp_field"`
	UUIDField       string `toml:"uuid_field"`
	SeverityField   string `toml:"severity_field"`
	TypeField       string `toml:"type_field"`
	LoggerField     string `toml:"logger_field"`
	EnvVersionField string `toml:"env_version_field"`
	HostnameField   string `toml:"hostname_field"`
	PIDField        string `toml:"pid_field"`

	ElasticsearchBulk  bool   `toml:"elasticsearch_bulk"`
	ElasticsearchIndex string `toml:"elasticsearch_index"`
	ElasticsearchType  string `toml:"elasticsearch_type"`
	ElasticsearchID    string `toml:"elasticsearch_id"`

	fieldMap map[string]fieldEncoder
}

// ConfigStruct is provided to make JSONEncoder implement the Heka pipeline.HasConfigStruct interface.
func (enc *JSONEncoder) ConfigStruct() interface{} {
	return &JSONEncoderConfig{
		ElasticsearchIndex: "heka-%{2006.01.02}",
		ElasticsearchType:  "%{Type}",
		ElasticsearchID:    "%{UUID}",
	}
}

// Init is provided to make JSONEncoder implement the Heka pipeline.Plugin interface.
func (enc *JSONEncoder) Init(config interface{}) (err error) {
	enc.config = config.(*JSONEncoderConfig)
	enc.config.buildFieldMap()
	enc.coord = &elasticsearch.ElasticSearchCoordinates{
		Index:                enc.config.ElasticsearchIndex,
		Type:                 enc.config.ElasticsearchType,
		Id:                   enc.config.ElasticsearchID,
		ESIndexFromTimestamp: true,
	}
	return
}

// Encode is implemented to make JSONEncoder implement the pipeline.Encoder interface.
func (enc *JSONEncoder) Encode(pack *pipeline.PipelinePack) (output []byte, err error) {
	enc.coord.Index = strftime.Format(enc.coord.Index, time.Now())
	rawMap := make(map[string]interface{})
	for _, field := range pack.Message.GetFields() {
		if field.GetValueType() == message.Field_BYTES && field.GetRepresentation() == "json" {
			rawMap[field.GetName()] = (*json.RawMessage)(&field.GetValueBytes()[0])
		} else {
			rawMap[field.GetName()] = field.GetValue()
		}
	}

	for _, encodeFn := range enc.config.fieldMap {
		encodeFn(rawMap, pack.Message)
	}

	buf := &bytes.Buffer{}
	if enc.config.ElasticsearchBulk {
		enc.coord.PopulateBuffer(pack.Message, buf)
		buf.WriteString("\n")
	}

	jsonEnc := json.NewEncoder(buf)
	err = jsonEnc.Encode(rawMap)
	output = buf.Bytes()
	return
}

func (conf *JSONEncoderConfig) encodeTimestamp(rawMap map[string]interface{}, msg *message.Message) {
	if msg.Timestamp != nil {
		rawMap[conf.TimestampField] = time.Unix(0, *msg.Timestamp).UTC()
	}
}

func (conf *JSONEncoderConfig) encodeUUID(rawMap map[string]interface{}, msg *message.Message) {
	if msg.Uuid != nil {
		rawMap[conf.UUIDField] = uuid.UUID(msg.Uuid).String()
	}
}

func (conf *JSONEncoderConfig) encodeSeverity(rawMap map[string]interface{}, msg *message.Message) {
	rawMap[conf.SeverityField] = msg.GetSeverity()
}

func (conf *JSONEncoderConfig) encodePID(rawMap map[string]interface{}, msg *message.Message) {
	if msg.Pid != nil {
		rawMap[conf.PIDField] = msg.GetPid()
	}
}

func (conf *JSONEncoderConfig) buildFieldMap() {
	conf.fieldMap = make(map[string]fieldEncoder)
	for _, f := range []struct {
		name string
		fn   fieldEncoder
	}{
		{conf.TimestampField, conf.encodeTimestamp},
		{conf.UUIDField, conf.encodeUUID},
		{conf.SeverityField, conf.encodeSeverity},
		{conf.PIDField, conf.encodePID},
		{conf.TypeField, conf.encodeStringField(conf.TypeField, (*message.Message).GetType)},
		{conf.LoggerField, conf.encodeStringField(conf.LoggerField, (*message.Message).GetLogger)},
		{conf.EnvVersionField, conf.encodeStringField(conf.EnvVersionField, (*message.Message).GetEnvVersion)},
		{conf.HostnameField, conf.encodeStringField(conf.HostnameField, (*message.Message).GetHostname)},
	} {
		if f.name != "" {
			conf.fieldMap[f.name] = f.fn
		}
	}
}

func (conf *JSONEncoderConfig) encodeStringField(fieldName string, getter func(*message.Message) string) fieldEncoder {
	return func(rawMap map[string]interface{}, msg *message.Message) {
		if val := getter(msg); val != "" {
			rawMap[fieldName] = val
		}
	}
}

func init() {
	pipeline.RegisterPlugin("JSONEncoder", func() interface{} { return new(JSONEncoder) })
}
<commit_msg>use timestamp field for index name<commit_after>package hekalocal

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

	"code.google.com/p/go-uuid/uuid"

	"github.com/hhkbp2/go-strftime"

	"github.com/mozilla-services/heka/message"
	"github.com/mozilla-services/heka/pipeline"
	"github.com/mozilla-services/heka/plugins/elasticsearch"
)

// JSONEncoder serializes messages to JSON.
type JSONEncoder struct {
	config *JSONEncoderConfig
	coord  *elasticsearch.ElasticSearchCoordinates
}

type fieldEncoder func(map[string]interface{}, *message.Message)

// JSONEncoderConfig contains the optional field names to put Message fields into.
type JSONEncoderConfig struct {
	TimestampField  string `toml:"timestamp_field"`
	UUIDField       string `toml:"uuid_field"`
	SeverityField   string `toml:"severity_field"`
	TypeField       string `toml:"type_field"`
	LoggerField     string `toml:"logger_field"`
	EnvVersionField string `toml:"env_version_field"`
	HostnameField   string `toml:"hostname_field"`
	PIDField        string `toml:"pid_field"`

	ElasticsearchBulk  bool   `toml:"elasticsearch_bulk"`
	ElasticsearchIndex string `toml:"elasticsearch_index"`
	ElasticsearchType  string `toml:"elasticsearch_type"`
	ElasticsearchID    string `toml:"elasticsearch_id"`

	fieldMap map[string]fieldEncoder
}

// ConfigStruct is provided to make JSONEncoder implement the Heka pipeline.HasConfigStruct interface.
func (enc *JSONEncoder) ConfigStruct() interface{} {
	return &JSONEncoderConfig{
		ElasticsearchIndex: "heka-%{2006.01.02}",
		ElasticsearchType:  "%{Type}",
		ElasticsearchID:    "%{UUID}",
	}
}

// Init is provided to make JSONEncoder implement the Heka pipeline.Plugin interface.
func (enc *JSONEncoder) Init(config interface{}) (err error) {
	enc.config = config.(*JSONEncoderConfig)
	enc.config.buildFieldMap()
	enc.coord = &elasticsearch.ElasticSearchCoordinates{
		Index:                enc.config.ElasticsearchIndex,
		Type:                 enc.config.ElasticsearchType,
		Id:                   enc.config.ElasticsearchID,
		ESIndexFromTimestamp: true,
	}
	return
}

// Encode is implemented to make JSONEncoder implement the pipeline.Encoder interface.
func (enc *JSONEncoder) Encode(pack *pipeline.PipelinePack) (output []byte, err error) {
	rawMap := make(map[string]interface{})
	for _, field := range pack.Message.GetFields() {
		if field.GetValueType() == message.Field_BYTES && field.GetRepresentation() == "json" {
			rawMap[field.GetName()] = (*json.RawMessage)(&field.GetValueBytes()[0])
		} else {
			rawMap[field.GetName()] = field.GetValue()
		}
	}

	for _, encodeFn := range enc.config.fieldMap {
		encodeFn(rawMap, pack.Message)
	}

	buf := &bytes.Buffer{}
	if enc.config.ElasticsearchBulk {
		enc.coord.Index = strftime.Format(enc.coord.Index, pack.Message.Timestamp)
		enc.coord.PopulateBuffer(pack.Message, buf)
		buf.WriteString("\n")
	}

	jsonEnc := json.NewEncoder(buf)
	err = jsonEnc.Encode(rawMap)
	output = buf.Bytes()
	return
}

func (conf *JSONEncoderConfig) encodeTimestamp(rawMap map[string]interface{}, msg *message.Message) {
	if msg.Timestamp != nil {
		rawMap[conf.TimestampField] = time.Unix(0, *msg.Timestamp).UTC()
	}
}

func (conf *JSONEncoderConfig) encodeUUID(rawMap map[string]interface{}, msg *message.Message) {
	if msg.Uuid != nil {
		rawMap[conf.UUIDField] = uuid.UUID(msg.Uuid).String()
	}
}

func (conf *JSONEncoderConfig) encodeSeverity(rawMap map[string]interface{}, msg *message.Message) {
	rawMap[conf.SeverityField] = msg.GetSeverity()
}

func (conf *JSONEncoderConfig) encodePID(rawMap map[string]interface{}, msg *message.Message) {
	if msg.Pid != nil {
		rawMap[conf.PIDField] = msg.GetPid()
	}
}

func (conf *JSONEncoderConfig) buildFieldMap() {
	conf.fieldMap = make(map[string]fieldEncoder)
	for _, f := range []struct {
		name string
		fn   fieldEncoder
	}{
		{conf.TimestampField, conf.encodeTimestamp},
		{conf.UUIDField, conf.encodeUUID},
		{conf.SeverityField, conf.encodeSeverity},
		{conf.PIDField, conf.encodePID},
		{conf.TypeField, conf.encodeStringField(conf.TypeField, (*message.Message).GetType)},
		{conf.LoggerField, conf.encodeStringField(conf.LoggerField, (*message.Message).GetLogger)},
		{conf.EnvVersionField, conf.encodeStringField(conf.EnvVersionField, (*message.Message).GetEnvVersion)},
		{conf.HostnameField, conf.encodeStringField(conf.HostnameField, (*message.Message).GetHostname)},
	} {
		if f.name != "" {
			conf.fieldMap[f.name] = f.fn
		}
	}
}

func (conf *JSONEncoderConfig) encodeStringField(fieldName string, getter func(*message.Message) string) fieldEncoder {
	return func(rawMap map[string]interface{}, msg *message.Message) {
		if val := getter(msg); val != "" {
			rawMap[fieldName] = val
		}
	}
}

func init() {
	pipeline.RegisterPlugin("JSONEncoder", func() interface{} { return new(JSONEncoder) })
}
<|endoftext|>


<commit_before>// SPDX-License-Identifier: MIT

package jwt

import (
	"errors"
	"fmt"
	"io/fs"
	"net/http"
	"strings"

	"github.com/golang-jwt/jwt/v4"
	"github.com/issue9/sliceutil"
	"github.com/issue9/web"
	"github.com/issue9/web/server"
)

const (
	contextKey contextKeyType = 1

	prefix = "bearer "

	prefixLen = 7 // len(prefix)
)

type (
	// Verifier JWT 验证器
	Verifier[T Claims] struct {
		blocker       Blocker[T]
		keyFunc       jwt.Keyfunc
		claimsBuilder BuildClaimsFunc[T]
		keys          []*key
	}

	BuildClaimsFunc[T Claims] func() T

	contextKeyType int
)

// NewVerifier 声明 Verifier 对象
//
// b 为处理丢弃令牌的对象，如果为空表示不会对任何令牌作特殊处理；
// f 为 Claims 对象的生成方法；
func NewVerifier[T Claims](b Blocker[T], f BuildClaimsFunc[T]) *Verifier[T] {
	if b == nil {
		b = defaultBlocker[T]{}
	}

	j := &Verifier[T]{
		blocker:       b,
		claimsBuilder: f,
		keys:          make([]*key, 0, 10),
	}

	j.keyFunc = func(t *jwt.Token) (any, error) {
		if len(j.keys) == 0 || len(t.Header) == 0 {
			return nil, ErrSigningMethodNotFound
		}

		if kid, found := t.Header["kid"]; found {
			if k, found := sliceutil.At(j.keys, func(e *key) bool { return e.id == kid }); found {
				t.Method = k.sign // 忽略由用户指定的 header['alg']，而是有 kid 指定。
				return k.key, nil
			}
		}

		return nil, ErrSigningMethodNotFound
	}

	return j
}

// Middleware 解码用户的 token 并写入 *web.Context
func (j *Verifier[T]) Middleware(next web.HandlerFunc) web.HandlerFunc {
	return func(ctx *server.Context) web.Responser {
		h := j.GetToken(ctx)

		if j.blocker.TokenIsBlocked(h) {
			return ctx.Status(http.StatusUnauthorized)
		}

		t, err := jwt.ParseWithClaims(h, j.claimsBuilder(), j.keyFunc)

		if errors.Is(err, &jwt.ValidationError{}) {
			ctx.Logs().ERROR().Error(err)
			return ctx.Status(http.StatusUnauthorized)
		} else if err != nil {
			return ctx.InternalServerError(err)
		}

		if !t.Valid {
			return ctx.Status(http.StatusUnauthorized)
		}

		if j.blocker.ClaimsIsBlocked(t.Claims.(T)) {
			return ctx.Status(http.StatusUnauthorized)
		}

		ctx.Vars[contextKey] = t.Claims

		return next(ctx)
	}
}

// GetValue 返回解码后的 Claims 对象
func (j *Verifier[T]) GetValue(ctx *web.Context) (T, bool) {
	v, found := ctx.Vars[contextKey]
	if !found {
		var vv T
		return vv, false
	}
	return v.(T), true
}

func (j Verifier[T]) GetToken(ctx *web.Context) string {
	h := ctx.Request().Header.Get("Authorization")
	if len(h) > prefixLen && strings.ToLower(h[:prefixLen]) == prefix {
		h = h[prefixLen:]
	}
	return h
}

// AddKey 添加证书
func (j *Verifier[T]) AddKey(id string, sign SigningMethod, keyData any) {
	if sliceutil.Exists(j.keys, func(e *key) bool { return e.id == id }) {
		panic(fmt.Sprintf("存在同名的签名方法 %s", id))
	}

	j.keys = append(j.keys, &key{
		id:   id,
		sign: sign,
		key:  keyData,
	})
}

func (j *Verifier[T]) AddHMAC(id string, sign *jwt.SigningMethodHMAC, secret []byte) {
	j.AddKey(id, sign, secret)
}

func (j *Verifier[T]) addRSA(id string, sign jwt.SigningMethod, public []byte) {
	pub, err := jwt.ParseRSAPublicKeyFromPEM(public)
	if err != nil {
		panic(err)
	}
	j.AddKey(id, sign, pub)
}

func (j *Verifier[T]) AddRSA(id string, sign *jwt.SigningMethodRSA, public []byte) {
	j.addRSA(id, sign, public)
}

func (j *Verifier[T]) AddRSAPSS(id string, sign *jwt.SigningMethodRSAPSS, public []byte) {
	j.addRSA(id, sign, public)
}

func (j *Verifier[T]) AddECDSA(id string, sign *jwt.SigningMethodECDSA, public []byte) {
	pub, err := jwt.ParseECPublicKeyFromPEM(public)
	if err != nil {
		panic(err)
	}
	j.AddKey(id, sign, pub)
}

func (j *Verifier[T]) AddEd25519(id string, sign *jwt.SigningMethodEd25519, public []byte) {
	pub, err := jwt.ParseEdPublicKeyFromPEM(public)
	if err != nil {
		panic(err)
	}

	j.AddKey(id, sign, pub)
}

func (j *Verifier[T]) AddRSAFromFS(id string, sign *jwt.SigningMethodRSA, fsys fs.FS, public string) {
	pub, err := fs.ReadFile(fsys, public)
	if err != nil {
		panic(err)
	}
	j.AddRSA(id, sign, pub)
}

func (j *Verifier[T]) AddRSAPSSFromFS(id string, sign *jwt.SigningMethodRSAPSS, fsys fs.FS, public string) {
	pub, err := fs.ReadFile(fsys, public)
	if err != nil {
		panic(err)
	}
	j.AddRSAPSS(id, sign, pub)
}

func (j *Verifier[T]) AddECDSAFromFS(id string, sign *jwt.SigningMethodECDSA, fsys fs.FS, public string) {
	pub, err := fs.ReadFile(fsys, public)
	if err != nil {
		panic(err)
	}
	j.AddECDSA(id, sign, pub)
}

func (j *Verifier[T]) AddEd25519FromFS(id string, sign *jwt.SigningMethodEd25519, fsys fs.FS, public string) {
	pub, err := fs.ReadFile(fsys, public)
	if err != nil {
		panic(err)
	}
	j.AddEd25519(id, sign, pub)
}
<commit_msg>refactor(jwt): 在不存在 Authorization 报头时返回 401<commit_after>// SPDX-License-Identifier: MIT

package jwt

import (
	"errors"
	"fmt"
	"io/fs"
	"net/http"
	"strings"

	"github.com/golang-jwt/jwt/v4"
	"github.com/issue9/sliceutil"
	"github.com/issue9/web"
	"github.com/issue9/web/server"
)

const (
	contextKey contextKeyType = 1

	prefix = "bearer "

	prefixLen = 7 // len(prefix)
)

type (
	// Verifier JWT 验证器
	Verifier[T Claims] struct {
		blocker       Blocker[T]
		keyFunc       jwt.Keyfunc
		claimsBuilder BuildClaimsFunc[T]
		keys          []*key
	}

	BuildClaimsFunc[T Claims] func() T

	contextKeyType int
)

// NewVerifier 声明 Verifier 对象
//
// b 为处理丢弃令牌的对象，如果为空表示不会对任何令牌作特殊处理；
// f 为 Claims 对象的生成方法；
func NewVerifier[T Claims](b Blocker[T], f BuildClaimsFunc[T]) *Verifier[T] {
	if b == nil {
		b = defaultBlocker[T]{}
	}

	j := &Verifier[T]{
		blocker:       b,
		claimsBuilder: f,
		keys:          make([]*key, 0, 10),
	}

	j.keyFunc = func(t *jwt.Token) (any, error) {
		if len(j.keys) == 0 || len(t.Header) == 0 {
			return nil, ErrSigningMethodNotFound
		}

		if kid, found := t.Header["kid"]; found {
			if k, found := sliceutil.At(j.keys, func(e *key) bool { return e.id == kid }); found {
				t.Method = k.sign // 忽略由用户指定的 header['alg']，而是有 kid 指定。
				return k.key, nil
			}
		}

		return nil, ErrSigningMethodNotFound
	}

	return j
}

// Middleware 解码用户的 token 并写入 *web.Context
func (j *Verifier[T]) Middleware(next web.HandlerFunc) web.HandlerFunc {
	return func(ctx *server.Context) web.Responser {
		h := j.GetToken(ctx)
		if h == "" || j.blocker.TokenIsBlocked(h) {
			return ctx.Status(http.StatusUnauthorized)
		}

		t, err := jwt.ParseWithClaims(h, j.claimsBuilder(), j.keyFunc)
		if errors.Is(err, &jwt.ValidationError{}) {
			ctx.Logs().ERROR().Error(err)
			return ctx.Status(http.StatusUnauthorized)
		} else if err != nil {
			return ctx.InternalServerError(err)
		}

		if !t.Valid {
			return ctx.Status(http.StatusUnauthorized)
		}

		if j.blocker.ClaimsIsBlocked(t.Claims.(T)) {
			return ctx.Status(http.StatusUnauthorized)
		}

		ctx.Vars[contextKey] = t.Claims

		return next(ctx)
	}
}

// GetValue 返回解码后的 Claims 对象
func (j *Verifier[T]) GetValue(ctx *web.Context) (T, bool) {
	// TODO: 如果支持泛型方法，那么泛化 GetValue 即可。
	v, found := ctx.Vars[contextKey]
	if !found {
		var vv T
		return vv, false
	}
	return v.(T), true
}

func (j Verifier[T]) GetToken(ctx *web.Context) string {
	h := ctx.Request().Header.Get("Authorization")
	if len(h) > prefixLen && strings.ToLower(h[:prefixLen]) == prefix {
		h = h[prefixLen:]
	}
	return h
}

// AddKey 添加证书
func (j *Verifier[T]) AddKey(id string, sign SigningMethod, keyData any) {
	if sliceutil.Exists(j.keys, func(e *key) bool { return e.id == id }) {
		panic(fmt.Sprintf("存在同名的签名方法 %s", id))
	}

	j.keys = append(j.keys, &key{
		id:   id,
		sign: sign,
		key:  keyData,
	})
}

func (j *Verifier[T]) AddHMAC(id string, sign *jwt.SigningMethodHMAC, secret []byte) {
	j.AddKey(id, sign, secret)
}

func (j *Verifier[T]) addRSA(id string, sign jwt.SigningMethod, public []byte) {
	pub, err := jwt.ParseRSAPublicKeyFromPEM(public)
	if err != nil {
		panic(err)
	}
	j.AddKey(id, sign, pub)
}

func (j *Verifier[T]) AddRSA(id string, sign *jwt.SigningMethodRSA, public []byte) {
	j.addRSA(id, sign, public)
}

func (j *Verifier[T]) AddRSAPSS(id string, sign *jwt.SigningMethodRSAPSS, public []byte) {
	j.addRSA(id, sign, public)
}

func (j *Verifier[T]) AddECDSA(id string, sign *jwt.SigningMethodECDSA, public []byte) {
	pub, err := jwt.ParseECPublicKeyFromPEM(public)
	if err != nil {
		panic(err)
	}
	j.AddKey(id, sign, pub)
}

func (j *Verifier[T]) AddEd25519(id string, sign *jwt.SigningMethodEd25519, public []byte) {
	pub, err := jwt.ParseEdPublicKeyFromPEM(public)
	if err != nil {
		panic(err)
	}

	j.AddKey(id, sign, pub)
}

func (j *Verifier[T]) AddRSAFromFS(id string, sign *jwt.SigningMethodRSA, fsys fs.FS, public string) {
	pub, err := fs.ReadFile(fsys, public)
	if err != nil {
		panic(err)
	}
	j.AddRSA(id, sign, pub)
}

func (j *Verifier[T]) AddRSAPSSFromFS(id string, sign *jwt.SigningMethodRSAPSS, fsys fs.FS, public string) {
	pub, err := fs.ReadFile(fsys, public)
	if err != nil {
		panic(err)
	}
	j.AddRSAPSS(id, sign, pub)
}

func (j *Verifier[T]) AddECDSAFromFS(id string, sign *jwt.SigningMethodECDSA, fsys fs.FS, public string) {
	pub, err := fs.ReadFile(fsys, public)
	if err != nil {
		panic(err)
	}
	j.AddECDSA(id, sign, pub)
}

func (j *Verifier[T]) AddEd25519FromFS(id string, sign *jwt.SigningMethodEd25519, fsys fs.FS, public string) {
	pub, err := fs.ReadFile(fsys, public)
	if err != nil {
		panic(err)
	}
	j.AddEd25519(id, sign, pub)
}
<|endoftext|>


<commit_before>package kodirpc_test

import (
	"fmt"
	"os"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/pdf/kodirpc"
)

func ExampleClient() {
	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil || client == nil {
		panic(err)
	}
}

func ExampleClient_logger() {
	// logrus is only used as an example, though loggers do need to satisfy the
	// LevelledLogger interface.
	logger := &logrus.Logger{
		Out:       os.Stdout,
		Formatter: &logrus.TextFormatter{},
		Hooks:     make(logrus.LevelHooks),
		Level:     logrus.WarnLevel,
	}
	kodirpc.SetLogger(logger)

	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil || client == nil {
		panic(err)
	}
}

func ExampleClient_config() {
	config := kodirpc.NewConfig()
	config.ReadTimeout = 2 * time.Second

	client, err := kodirpc.NewClient(`127.0.0.1:9090`, config)
	if err != nil || client == nil {
		panic(err)
	}
}

func ExampleClient_Call() {
	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil {
		panic(err)
	}
	defer func() {
		if err = client.Close(); err != nil {
			panic(err)
		}
	}()

	params := map[string]interface{}{
		`title`:       `Hello`,
		`message`:     `From kodirpc`,
		`displaytime`: 5000,
	}
	res, err := client.Call(`GUI.ShowNotification`, params)
	if err != nil {
		panic(err)
	}

	fmt.Println(res)
}

func ExampleClient_Notify() {
	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil {
		panic(err)
	}
	defer func() {
		if err = client.Close(); err != nil {
			panic(err)
		}
	}()

	params := map[string]interface{}{
		`title`:       `Hello`,
		`message`:     `From kodirpc`,
		`displaytime`: 5000,
	}
	err = client.Notify(`GUI.ShowNotification`, params)
	if err != nil {
		panic(err)
	}
}

func ExampleClient_Handle() {
	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil {
		panic(err)
	}
	defer func() {
		if err = client.Close(); err != nil {
			panic(err)
		}
	}()

	// Handle will execute the handler func every time a notification with the
	// specified method is sent.
	client.Handle(`VideoLibrary.OnUpdate`, func(method string, data interface{}) {
		fmt.Println(data.(map[string]interface{}))
	})

	// Your application loop/logic should go here, the following will block
	// indefinitely
	done := make(chan struct{})
	<-done
}
<commit_msg>Attach the logger example to the SetLogger func<commit_after>package kodirpc_test

import (
	"fmt"
	"os"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/pdf/kodirpc"
)

func ExampleClient() {
	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil || client == nil {
		panic(err)
	}
}

func ExampleClient_config() {
	config := kodirpc.NewConfig()
	config.ReadTimeout = 2 * time.Second

	client, err := kodirpc.NewClient(`127.0.0.1:9090`, config)
	if err != nil || client == nil {
		panic(err)
	}
}

func ExampleSetLogger() {
	// logrus is only used as an example, though loggers do need to satisfy the
	// LevelledLogger interface.
	logger := &logrus.Logger{
		Out:       os.Stdout,
		Formatter: &logrus.TextFormatter{},
		Hooks:     make(logrus.LevelHooks),
		Level:     logrus.WarnLevel,
	}
	kodirpc.SetLogger(logger)

	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil || client == nil {
		panic(err)
	}
}

func ExampleClient_Call() {
	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil {
		panic(err)
	}
	defer func() {
		if err = client.Close(); err != nil {
			panic(err)
		}
	}()

	params := map[string]interface{}{
		`title`:       `Hello`,
		`message`:     `From kodirpc`,
		`displaytime`: 5000,
	}
	res, err := client.Call(`GUI.ShowNotification`, params)
	if err != nil {
		panic(err)
	}

	fmt.Println(res)
}

func ExampleClient_Notify() {
	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil {
		panic(err)
	}
	defer func() {
		if err = client.Close(); err != nil {
			panic(err)
		}
	}()

	params := map[string]interface{}{
		`title`:       `Hello`,
		`message`:     `From kodirpc`,
		`displaytime`: 5000,
	}
	err = client.Notify(`GUI.ShowNotification`, params)
	if err != nil {
		panic(err)
	}
}

func ExampleClient_Handle() {
	client, err := kodirpc.NewClient(`127.0.0.1:9090`, kodirpc.NewConfig())
	if err != nil {
		panic(err)
	}
	defer func() {
		if err = client.Close(); err != nil {
			panic(err)
		}
	}()

	// Handle will execute the handler func every time a notification with the
	// specified method is sent.
	client.Handle(`VideoLibrary.OnUpdate`, func(method string, data interface{}) {
		fmt.Println(data.(map[string]interface{}))
	})

	// Your application loop/logic should go here, the following will block
	// indefinitely
	done := make(chan struct{})
	<-done
}
<|endoftext|>


<commit_before>package layers

import (
	"code.google.com/p/gopacket"
	"fmt"
)

// Checksum computation for TCP/UDP.
type tcpipchecksum struct {
	pseudoheader tcpipPseudoHeader
}

type tcpipPseudoHeader interface {
	pseudoheaderChecksum() uint32
}

func (ip *IPv4) pseudoheaderChecksum() (csum uint32) {
	csum += (uint32(ip.SrcIP[0]) + uint32(ip.SrcIP[2])) << 8
	csum += uint32(ip.SrcIP[1]) + uint32(ip.SrcIP[3])
	csum += (uint32(ip.DstIP[0]) + uint32(ip.DstIP[2])) << 8
	csum += uint32(ip.DstIP[1]) + uint32(ip.DstIP[3])
	csum += uint32(ip.Protocol)
	return
}

func (ip *IPv6) pseudoheaderChecksum() (csum uint32) {
	for i := 0; i < 16; i += 2 {
		csum += uint32(ip.SrcIP[i]) << 8
		csum += uint32(ip.SrcIP[i+1])
		csum += uint32(ip.DstIP[i]) << 8
		csum += uint32(ip.DstIP[i+1])
	}
	csum += uint32(ip.NextHeader)
	return
}

// Calculate the TCP/IP checksum defined in rfc1071.  The passed-in csum is any
// initial checksum data that's already been computed.
func tcpipChecksum(data []byte, csum uint32) uint16 {
	// to handle odd lengths, we loop to length - 1, incrementing by 2, then
	// handle the last byte specifically by checking against the original
	// length.
	length := len(data) - 1
	for i := 0; i < length; i += 2 {
		// For our test packet, doing this manually is about 25% faster
		// (740 ns vs. 1000ns) than doing it by calling binary.BigEndian.Uint16.
		csum += uint32(data[i]) << 8
		csum += uint32(data[i+1])
	}
	if len(data)%2 == 1 {
		csum += uint32(data[length] << 8)
	}
	for csum > 0xffff {
		csum = (csum >> 16) + (csum & 0xffff)
	}
	return ^uint16(csum + (csum >> 16))
}

// computeChecksum computes a TCP or UDP checksum.  headerAndPayload is the
// serialized TCP or UDP header plus its payload, with the checksum zero'd
// out.
func (c *tcpipchecksum) computeChecksum(headerAndPayload []byte) (uint16, error) {
	if c.pseudoheader == nil {
		return 0, fmt.Errorf("TCP/IP layer 4 checksum cannot be computed without network layer... call SetNetworkLayerForChecksum to set which layer to use")
	}
	length := uint32(len(headerAndPayload))
	csum := c.pseudoheader.pseudoheaderChecksum()
	csum += length & 0xffff
	csum += length >> 16
	return tcpipChecksum(headerAndPayload, csum), nil
}

// SetNetworkLayerForChecksum tells this layer which network layer is wrapping it.
// This is needed for computing the checksum when serializing, since TCP/IP transport
// layer checksums depends on fields in the IPv4 or IPv6 layer that contains it.
// The passed in layer must be an *IPv4 or *IPv6.
func (i *tcpipchecksum) SetNetworkLayerForChecksum(l gopacket.NetworkLayer) error {
	switch v := l.(type) {
	case *IPv4:
		i.pseudoheader = v
	case *IPv6:
		i.pseudoheader = v
	default:
		return fmt.Errorf("cannot use layer type %v for tcp checksum network layer", l.LayerType())
	}
	return nil
}
<commit_msg>Fix TCP checksum calculation.<commit_after>package layers

import (
	"code.google.com/p/gopacket"
	"fmt"
)

// Checksum computation for TCP/UDP.
type tcpipchecksum struct {
	pseudoheader tcpipPseudoHeader
}

type tcpipPseudoHeader interface {
	pseudoheaderChecksum() uint32
}

func (ip *IPv4) pseudoheaderChecksum() (csum uint32) {
	csum += (uint32(ip.SrcIP[0]) + uint32(ip.SrcIP[2])) << 8
	csum += uint32(ip.SrcIP[1]) + uint32(ip.SrcIP[3])
	csum += (uint32(ip.DstIP[0]) + uint32(ip.DstIP[2])) << 8
	csum += uint32(ip.DstIP[1]) + uint32(ip.DstIP[3])
	csum += uint32(ip.Protocol)
	return
}

func (ip *IPv6) pseudoheaderChecksum() (csum uint32) {
	for i := 0; i < 16; i += 2 {
		csum += uint32(ip.SrcIP[i]) << 8
		csum += uint32(ip.SrcIP[i+1])
		csum += uint32(ip.DstIP[i]) << 8
		csum += uint32(ip.DstIP[i+1])
	}
	csum += uint32(ip.NextHeader)
	return
}

// Calculate the TCP/IP checksum defined in rfc1071.  The passed-in csum is any
// initial checksum data that's already been computed.
func tcpipChecksum(data []byte, csum uint32) uint16 {
	// to handle odd lengths, we loop to length - 1, incrementing by 2, then
	// handle the last byte specifically by checking against the original
	// length.
	length := len(data) - 1
	for i := 0; i < length; i += 2 {
		// For our test packet, doing this manually is about 25% faster
		// (740 ns vs. 1000ns) than doing it by calling binary.BigEndian.Uint16.
		csum += uint32(data[i]) << 8
		csum += uint32(data[i+1])
	}
	if len(data)%2 == 1 {
		csum += uint32(data[length]) << 8
	}
	for csum > 0xffff {
		csum = (csum >> 16) + (csum & 0xffff)
	}
	return ^uint16(csum + (csum >> 16))
}

// computeChecksum computes a TCP or UDP checksum.  headerAndPayload is the
// serialized TCP or UDP header plus its payload, with the checksum zero'd
// out.
func (c *tcpipchecksum) computeChecksum(headerAndPayload []byte) (uint16, error) {
	if c.pseudoheader == nil {
		return 0, fmt.Errorf("TCP/IP layer 4 checksum cannot be computed without network layer... call SetNetworkLayerForChecksum to set which layer to use")
	}
	length := uint32(len(headerAndPayload))
	csum := c.pseudoheader.pseudoheaderChecksum()
	csum += length & 0xffff
	csum += length >> 16
	return tcpipChecksum(headerAndPayload, csum), nil
}

// SetNetworkLayerForChecksum tells this layer which network layer is wrapping it.
// This is needed for computing the checksum when serializing, since TCP/IP transport
// layer checksums depends on fields in the IPv4 or IPv6 layer that contains it.
// The passed in layer must be an *IPv4 or *IPv6.
func (i *tcpipchecksum) SetNetworkLayerForChecksum(l gopacket.NetworkLayer) error {
	switch v := l.(type) {
	case *IPv4:
		i.pseudoheader = v
	case *IPv6:
		i.pseudoheader = v
	default:
		return fmt.Errorf("cannot use layer type %v for tcp checksum network layer", l.LayerType())
	}
	return nil
}
<|endoftext|>