<commit_before>package mp

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"
)

type MediaPlayer struct {
	player       Backend
	stateChange  chan StateChange
	volumeChange chan int

	// Two channels to handle passing of the play state without causing race
	// conditions.
	playstateChan     chan PlayState
	playstateChanPing chan bool

	streams map[string]string // map of YouTube ID to stream gotten from youtube-dl
}

func New(stateChange chan StateChange, volumeChange chan int) *MediaPlayer {
	p := MediaPlayer{}

	p.playstateChan = make(chan PlayState)
	p.playstateChanPing = make(chan bool)
	p.stateChange = stateChange
	p.volumeChange = volumeChange
	p.streams = make(map[string]string)

	p.player = &VLC{}
	playerEventChan := p.player.initialize()

	go p.run(playerEventChan)

	return &p
}

func (p *MediaPlayer) Quit() {
	go func() {
		// TODO: fix race conditions (the player might not be fully initialized yet)
		p.player.quit()
	}()

	// TODO: fix race conditions
	close(p.stateChange)
	close(p.volumeChange)
}

func (p *MediaPlayer) GetPosition() time.Duration {
	ps := p.GetPlaystate()

	return p.getPosition(ps)
}

func (p *MediaPlayer) getPosition(ps *PlayState) time.Duration {
	var position time.Duration
	if ps.State == STATE_STOPPED {
		position = 0
	} else if ps.State == STATE_BUFFERING {
		position = ps.bufferingPosition
	} else {
		position = p.player.getPosition()
	}

	if position < 0 {
		panic("got position < 0")
	}

	return position
}

// GetPlaystate returns the play state synchronously (may block)
func (p *MediaPlayer) GetPlaystate() *PlayState {
	p.playstateChanPing <- false
	ps := <-p.playstateChan
	return &ps
}

// changePlaystate changes the play state inside a callback
// The *PlayState argument in the callback is the PlayState that can be
// changed, but it can only be accessed inside the callback (outside of
// it, race conditions can occur).
func (p *MediaPlayer) changePlaystate(callback func(*PlayState)) {
	p.playstateChanPing <- true
	ps := <-p.playstateChan
	callback(&ps)
	p.playstateChan <- ps
}

// SetPlaystate changes the play state to the specified arguments
// This function doesn't block, but changes may not be immediately applied.
func (p *MediaPlayer) SetPlaystate(playlist []string, index int, position time.Duration) {
	go p.changePlaystate(func(ps *PlayState) {
		if ps.State == STATE_BUFFERING && ps.bufferingPosition == position && playlist[index] == ps.Playlist[ps.Index] {
			// just in case something else has changed, update the playlist
			p.updatePlaylist(ps, playlist)
			return
		}
		ps.Playlist = playlist
		ps.Index = index

		if len(ps.Playlist) > 0 {
			p.startPlaying(ps, position)
		} else {
			p.Stop()
		}
	})
}

func (p *MediaPlayer) startPlaying(ps *PlayState, position time.Duration) {
	p.setPlayState(ps, STATE_BUFFERING, position)

	videoId := ps.Playlist[ps.Index]

	// do not use the playstate inside this goroutine to prevent race conditions
	go func() {
		streamUrl := p.getYouTubeStream(videoId)
		p.player.play(streamUrl, position)
	}()
}

func (p *MediaPlayer) getYouTubeStream(videoId string) string {
	if stream, ok := p.streams[videoId]; ok {
		return stream
	}

	youtubeUrl := "https://www.youtube.com/watch?v=" + videoId

	fmt.Println("Fetching YouTube stream...", youtubeUrl)
	// First (mkv-container) audio only, then video with audio bitrate 100+
	// (where video has the lowest possible quality), then slightly lower
	// quality audio.
	// We do this because for some reason DASH aac audio (in the MP4 container)
	// doesn't support seeking in any of the tested players (mpv using
	// libavformat, and vlc, gstreamer and mplayer2 using their own demuxers).
	// But the MKV container seems to have much better support.
	// See:
	//   https://github.com/mpv-player/mpv/issues/579
	//   https://trac.ffmpeg.org/ticket/3842
	cmd := exec.Command("youtube-dl", "-g", "-f", "171/172/43/22/18", youtubeUrl)
	cmd.Stderr = os.Stderr
	output, err := cmd.Output()
	fmt.Println("Got stream.")
	if err != nil {
		panic(err)
	}
	stream := strings.TrimSpace(string(output))
	p.streams[videoId] = stream
	return stream
}

// setPlayState sets updates the PlayState and sends events.
// position may be -1: in that case it will be updated.
func (p *MediaPlayer) setPlayState(ps *PlayState, state State, position time.Duration) {
	ps.State = state

	if state == STATE_BUFFERING {
		ps.bufferingPosition = position
	} else {
		ps.bufferingPosition = -1
	}

	if position == -1 {
		position = p.getPosition(ps)
	}

	go func() {
		p.stateChange <- StateChange{state, position}
	}()
}

func (p *MediaPlayer) UpdatePlaylist(playlist []string) {
	go p.changePlaystate(func(ps *PlayState) {
		p.updatePlaylist(ps, playlist)
	})
}

func (p *MediaPlayer) updatePlaylist(ps *PlayState, playlist []string) {
	if len(ps.Playlist) == 0 {

		if ps.State == STATE_PLAYING {
			// just to be sure
			panic("empty playlist while playing")
		}
		ps.Playlist = playlist

		if ps.Index >= len(playlist) {
			// this appears to be the normal behavior of YouTube
			ps.Index = len(playlist) - 1
		}

		if ps.State == STATE_STOPPED {
			p.startPlaying(ps, 0)
		}

	} else {
		videoId := ps.Playlist[ps.Index]
		ps.Playlist = playlist
		p.setPlaylistIndex(ps, videoId)
	}
}

func (p *MediaPlayer) SetVideo(videoId string, position time.Duration) {
	go p.changePlaystate(func(ps *PlayState) {
		p.setPlaylistIndex(ps, videoId)
		p.startPlaying(ps, position)
	})
}

func (p *MediaPlayer) setPlaylistIndex(ps *PlayState, videoId string) {
	newIndex := -1
	for i, v := range ps.Playlist {
		if v == videoId {
			if newIndex >= 0 {
				fmt.Fprintln(os.Stderr, "WARNING: videoId exists twice in playlist")
				break
			}
			newIndex = i
			// no 'break' so duplicate video entries can be checked
		}
	}

	if newIndex == -1 {
		// don't know how to proceed
		panic("current video does not exist in new playlist")
	}

	ps.Index = newIndex
}

// Pause pauses the currently playing video
func (p *MediaPlayer) Pause() {
	go p.changePlaystate(func(ps *PlayState) {
		if ps.State != STATE_PLAYING {
			fmt.Printf("Warning: pause while in state %d\n", ps.State)
		}

		p.player.pause()
	})
}

// Resume resumes playback when it was paused
func (p *MediaPlayer) Resume() {
	go p.changePlaystate(func(ps *PlayState) {
		if ps.State != STATE_PAUSED {
			fmt.Printf("Warning: resume while in state %d\n", ps.State)
		}

		p.player.resume()
	})
}

// Seek jumps to the specified position
func (p *MediaPlayer) Seek(position time.Duration) {
	go p.changePlaystate(func(ps *PlayState) {
		if ps.State != STATE_PAUSED {
			fmt.Printf("Warning: state is not paused while seeking (state: %d)\n", ps.State)
		}

		p.player.setPosition(position)
	})
}

// SetVolume sets the volume of the player to the specified value (0-100).
func (p *MediaPlayer) SetVolume(volume int) {
	go p.changePlaystate(func(ps *PlayState) {
		ps.Volume = volume
		p.player.setVolume(ps.Volume)
	})
}

// ChangeVolume increases or decreases the volume by the specified delta.
// It returns a channel with the result, that can be ignored.
func (p *MediaPlayer) ChangeVolume(delta int) chan int {
	// the buffer makes sure a send will succeed even if the channel is never
	// read: this way the channel can be garbage collected and won't leave a
	// garbage goroutine if it is never read.
	c := make(chan int, 1)
	go p.changePlaystate(func(ps *PlayState) {

		ps.Volume += delta
		// pressing 'volume up' or 'volume down' keeps sending volume
		// increase/decrease messages. Keep the volume within range 0-100.
		if ps.Volume < 0 {
			ps.Volume = 0
		}
		if ps.Volume > 100 {
			ps.Volume = 100
		}

		p.player.setVolume(ps.Volume)

		c <- ps.Volume
	})
	return c
}

func (p *MediaPlayer) Stop() {
	go p.changePlaystate(func(ps *PlayState) {
		ps.Playlist = []string{}
		// Do not set ps.Index to 0, it may be needed for UpdatePlaylist:
		// Stop is called before UpdatePlaylist when removing the currently
		// playing video from the playlist.
		p.player.stop()
	})
}

func (p *MediaPlayer) run(playerEventChan chan State) {
	ps := PlayState{}

	ps.Volume = INITIAL_VOLUME

	for {
		select {
		case replace := <-p.playstateChanPing:
			p.playstateChan <- ps
			if replace {
				ps = <-p.playstateChan
			}

		case event, ok := <-playerEventChan:
			if !ok {
				// player has quit, and closed channel
				return
			}

			switch event {
			case STATE_PLAYING:
				p.setPlayState(&ps, STATE_PLAYING, -1)

			case STATE_PAUSED:
				p.setPlayState(&ps, STATE_PAUSED, -1)

			case STATE_STOPPED:
				if ps.State == STATE_BUFFERING {
					// Especially VLC may keep sending 'stopped' events
					// while the next track is already buffering.
					// Ignore those events.
					continue
				}
				if ps.Index+1 < len(ps.Playlist) {
					// there are more videos, play the next
					ps.Index++
					p.startPlaying(&ps, 0)
				} else {
					// signal that the video has stopped playing
					// this resets the position but keeps the playlist
					p.setPlayState(&ps, STATE_STOPPED, 0)
				}
			}
		}
	}
}
<commit_msg>Warning<commit_after>package mp

import (
	"fmt"
	"os"
	"os/exec"
	"strings"
	"time"
)

type MediaPlayer struct {
	player       Backend
	stateChange  chan StateChange
	volumeChange chan int

	// Two channels to handle passing of the play state without causing race
	// conditions.
	playstateChan     chan PlayState
	playstateChanPing chan bool

	streams map[string]string // map of YouTube ID to stream gotten from youtube-dl
}

func New(stateChange chan StateChange, volumeChange chan int) *MediaPlayer {
	p := MediaPlayer{}

	p.playstateChan = make(chan PlayState)
	p.playstateChanPing = make(chan bool)
	p.stateChange = stateChange
	p.volumeChange = volumeChange
	p.streams = make(map[string]string)

	p.player = &VLC{}
	playerEventChan := p.player.initialize()

	go p.run(playerEventChan)

	return &p
}

func (p *MediaPlayer) Quit() {
	go func() {
		// TODO: fix race conditions (the player might not be fully initialized yet)
		p.player.quit()
	}()

	// TODO: fix race conditions
	close(p.stateChange)
	close(p.volumeChange)
}

func (p *MediaPlayer) GetPosition() time.Duration {
	ps := p.GetPlaystate()

	return p.getPosition(ps)
}

func (p *MediaPlayer) getPosition(ps *PlayState) time.Duration {
	var position time.Duration
	if ps.State == STATE_STOPPED {
		position = 0
	} else if ps.State == STATE_BUFFERING {
		position = ps.bufferingPosition
	} else {
		position = p.player.getPosition()
	}

	if position < 0 {
		panic("got position < 0")
	}

	return position
}

// GetPlaystate returns the play state synchronously (may block)
func (p *MediaPlayer) GetPlaystate() *PlayState {
	p.playstateChanPing <- false
	ps := <-p.playstateChan
	return &ps
}

// changePlaystate changes the play state inside a callback
// The *PlayState argument in the callback is the PlayState that can be
// changed, but it can only be accessed inside the callback (outside of
// it, race conditions can occur).
func (p *MediaPlayer) changePlaystate(callback func(*PlayState)) {
	p.playstateChanPing <- true
	ps := <-p.playstateChan
	callback(&ps)
	p.playstateChan <- ps
}

// SetPlaystate changes the play state to the specified arguments
// This function doesn't block, but changes may not be immediately applied.
func (p *MediaPlayer) SetPlaystate(playlist []string, index int, position time.Duration) {
	go p.changePlaystate(func(ps *PlayState) {
		if ps.State == STATE_BUFFERING && ps.bufferingPosition == position && playlist[index] == ps.Playlist[ps.Index] {
			// just in case something else has changed, update the playlist
			p.updatePlaylist(ps, playlist)
			return
		}
		ps.Playlist = playlist
		ps.Index = index

		if len(ps.Playlist) > 0 {
			p.startPlaying(ps, position)
		} else {
			p.Stop()
		}
	})
}

func (p *MediaPlayer) startPlaying(ps *PlayState, position time.Duration) {
	p.setPlayState(ps, STATE_BUFFERING, position)

	videoId := ps.Playlist[ps.Index]

	// do not use the playstate inside this goroutine to prevent race conditions
	go func() {
		streamUrl := p.getYouTubeStream(videoId)
		p.player.play(streamUrl, position)
	}()
}

func (p *MediaPlayer) getYouTubeStream(videoId string) string {
	if stream, ok := p.streams[videoId]; ok {
		return stream
	}

	youtubeUrl := "https://www.youtube.com/watch?v=" + videoId

	fmt.Println("Fetching YouTube stream...", youtubeUrl)
	// First (mkv-container) audio only, then video with audio bitrate 100+
	// (where video has the lowest possible quality), then slightly lower
	// quality audio.
	// We do this because for some reason DASH aac audio (in the MP4 container)
	// doesn't support seeking in any of the tested players (mpv using
	// libavformat, and vlc, gstreamer and mplayer2 using their own demuxers).
	// But the MKV container seems to have much better support.
	// See:
	//   https://github.com/mpv-player/mpv/issues/579
	//   https://trac.ffmpeg.org/ticket/3842
	cmd := exec.Command("youtube-dl", "-g", "-f", "171/172/43/22/18", youtubeUrl)
	cmd.Stderr = os.Stderr
	output, err := cmd.Output()
	fmt.Println("Got stream.")
	if err != nil {
		panic(err)
	}
	stream := strings.TrimSpace(string(output))
	p.streams[videoId] = stream
	return stream
}

// setPlayState sets updates the PlayState and sends events.
// position may be -1: in that case it will be updated.
func (p *MediaPlayer) setPlayState(ps *PlayState, state State, position time.Duration) {
	ps.State = state

	if state == STATE_BUFFERING {
		ps.bufferingPosition = position
	} else {
		ps.bufferingPosition = -1
	}

	if position == -1 {
		position = p.getPosition(ps)
	}

	go func() {
		p.stateChange <- StateChange{state, position}
	}()
}

func (p *MediaPlayer) UpdatePlaylist(playlist []string) {
	go p.changePlaystate(func(ps *PlayState) {
		p.updatePlaylist(ps, playlist)
	})
}

func (p *MediaPlayer) updatePlaylist(ps *PlayState, playlist []string) {
	if len(ps.Playlist) == 0 {

		if ps.State == STATE_PLAYING {
			// just to be sure
			panic("empty playlist while playing")
		}
		ps.Playlist = playlist

		if ps.Index >= len(playlist) {
			// this appears to be the normal behavior of YouTube
			ps.Index = len(playlist) - 1
		}

		if ps.State == STATE_STOPPED {
			p.startPlaying(ps, 0)
		}

	} else {
		videoId := ps.Playlist[ps.Index]
		ps.Playlist = playlist
		p.setPlaylistIndex(ps, videoId)
	}
}

func (p *MediaPlayer) SetVideo(videoId string, position time.Duration) {
	go p.changePlaystate(func(ps *PlayState) {
		p.setPlaylistIndex(ps, videoId)
		p.startPlaying(ps, position)
	})
}

func (p *MediaPlayer) setPlaylistIndex(ps *PlayState, videoId string) {
	newIndex := -1
	for i, v := range ps.Playlist {
		if v == videoId {
			if newIndex >= 0 {
				fmt.Fprintln(os.Stderr, "WARNING: videoId exists twice in playlist")
				break
			}
			newIndex = i
			// no 'break' so duplicate video entries can be checked
		}
	}

	if newIndex == -1 {
		// don't know how to proceed
		panic("current video does not exist in new playlist")
	}

	ps.Index = newIndex
}

// Pause pauses the currently playing video
func (p *MediaPlayer) Pause() {
	go p.changePlaystate(func(ps *PlayState) {
		if ps.State != STATE_PLAYING {
			fmt.Printf("Warning: pause while in state %d\n", ps.State)
		}

		p.player.pause()
	})
}

// Resume resumes playback when it was paused
func (p *MediaPlayer) Resume() {
	go p.changePlaystate(func(ps *PlayState) {
		if ps.State != STATE_PAUSED {
			fmt.Printf("Warning: resume while in state %d\n", ps.State)
		}

		p.player.resume()
	})
}

// Seek jumps to the specified position
func (p *MediaPlayer) Seek(position time.Duration) {
	go p.changePlaystate(func(ps *PlayState) {
		if ps.State != STATE_PAUSED {
			fmt.Printf("Warning: state is not paused while seeking (state: %d)\n", ps.State)
		}

		p.player.setPosition(position)
	})
}

// SetVolume sets the volume of the player to the specified value (0-100).
func (p *MediaPlayer) SetVolume(volume int) {
	go p.changePlaystate(func(ps *PlayState) {
		ps.Volume = volume
		p.player.setVolume(ps.Volume)
	})
}

// ChangeVolume increases or decreases the volume by the specified delta.
// It returns a channel with the result, that can be ignored.
func (p *MediaPlayer) ChangeVolume(delta int) chan int {
	// the buffer makes sure a send will succeed even if the channel is never
	// read: this way the channel can be garbage collected and won't leave a
	// garbage goroutine if it is never read.
	c := make(chan int, 1)
	go p.changePlaystate(func(ps *PlayState) {

		ps.Volume += delta
		// pressing 'volume up' or 'volume down' keeps sending volume
		// increase/decrease messages. Keep the volume within range 0-100.
		if ps.Volume < 0 {
			ps.Volume = 0
		}
		if ps.Volume > 100 {
			ps.Volume = 100
		}

		p.player.setVolume(ps.Volume)

		c <- ps.Volume
	})
	return c
}

func (p *MediaPlayer) Stop() {
	go p.changePlaystate(func(ps *PlayState) {
		ps.Playlist = []string{}
		// Do not set ps.Index to 0, it may be needed for UpdatePlaylist:
		// Stop is called before UpdatePlaylist when removing the currently
		// playing video from the playlist.
		p.player.stop()
	})
}

func (p *MediaPlayer) run(playerEventChan chan State) {
	ps := PlayState{}

	ps.Volume = INITIAL_VOLUME

	for {
		select {
		case replace := <-p.playstateChanPing:
			p.playstateChan <- ps
			if replace {
				ps = <-p.playstateChan
			}

		case event, ok := <-playerEventChan:
			if !ok {
				// player has quit, and closed channel
				return
			}

			switch event {
			case STATE_PLAYING:
				p.setPlayState(&ps, STATE_PLAYING, -1)

			case STATE_PAUSED:
				p.setPlayState(&ps, STATE_PAUSED, -1)

			case STATE_STOPPED:
				if ps.State == STATE_BUFFERING {
					// Especially VLC may keep sending 'stopped' events
					// while the next track is already buffering.
					// Ignore those events.
					fmt.Println("WARNING: 'stopped' event while buffering")
					continue
				}
				if ps.Index+1 < len(ps.Playlist) {
					// there are more videos, play the next
					ps.Index++
					p.startPlaying(&ps, 0)
				} else {
					// signal that the video has stopped playing
					// this resets the position but keeps the playlist
					p.setPlayState(&ps, STATE_STOPPED, 0)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before><commit_msg>Remove unused imports<commit_after><|endoftext|>


<commit_before><commit_msg>async cache load<commit_after><|endoftext|>


<commit_before><commit_msg>Add kbd toggle button.<commit_after><|endoftext|>


<commit_before><commit_msg>cmd/tivarename: clean main<commit_after><|endoftext|>


<commit_before>/*
Copyright 2018 The Knative Authors

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

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

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

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/knative/pkg/configmap"
	"github.com/knative/pkg/controller"
	"github.com/knative/pkg/logging/logkey"
	pkgmetrics "github.com/knative/pkg/metrics"
	"github.com/knative/pkg/signals"
	"github.com/knative/pkg/system"
	"github.com/knative/pkg/version"
	"github.com/knative/pkg/websocket"
	"github.com/knative/serving/cmd/util"
	"github.com/knative/serving/pkg/activator"
	activatorconfig "github.com/knative/serving/pkg/activator/config"
	activatorhandler "github.com/knative/serving/pkg/activator/handler"
	"github.com/knative/serving/pkg/apis/networking"
	nv1a1 "github.com/knative/serving/pkg/apis/networking/v1alpha1"
	"github.com/knative/serving/pkg/apis/serving"
	"github.com/knative/serving/pkg/apis/serving/v1alpha1"
	"github.com/knative/serving/pkg/autoscaler"
	clientset "github.com/knative/serving/pkg/client/clientset/versioned"
	servinginformers "github.com/knative/serving/pkg/client/informers/externalversions"
	"github.com/knative/serving/pkg/goversion"
	pkghttp "github.com/knative/serving/pkg/http"
	"github.com/knative/serving/pkg/logging"
	"github.com/knative/serving/pkg/metrics"
	"github.com/knative/serving/pkg/network"
	"github.com/knative/serving/pkg/queue"
	"github.com/knative/serving/pkg/reconciler"
	"github.com/knative/serving/pkg/resources"
	"github.com/knative/serving/pkg/tracing"
	tracingconfig "github.com/knative/serving/pkg/tracing/config"
	zipkin "github.com/openzipkin/zipkin-go"
	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	kubeinformers "k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/clientcmd"
)

// Fail if using unsupported go version.
var _ = goversion.IsSupported()

const (
	component = "activator"

	// This is the number of times we will perform network probes to
	// see if the Revision is accessible before forwarding the actual
	// request.
	maxRetries = 18

	// Add a little buffer space between request handling and stat
	// reporting so that latency in the stat pipeline doesn't
	// interfere with request handling.
	statReportingQueueLength = 10

	// Add enough buffer to not block request serving on stats collection
	requestCountingQueueLength = 100

	// The number of requests that are queued on the breaker before the 503s are sent.
	// The value must be adjusted depending on the actual production requirements.
	breakerQueueDepth = 10000

	// The upper bound for concurrent requests sent to the revision.
	// As new endpoints show up, the Breakers concurrency increases up to this value.
	breakerMaxConcurrency = 1000

	// The port on which autoscaler WebSocket server listens.
	autoscalerPort = 8080

	defaultResyncInterval = 10 * time.Hour
)

var (
	masterURL = flag.String("master", "", "The address of the Kubernetes API server. "+
		"Overrides any value in kubeconfig. Only required if out-of-cluster.")
	kubeconfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
)

func statReporter(statSink *websocket.ManagedConnection, stopCh <-chan struct{},
	statChan <-chan *autoscaler.StatMessage, logger *zap.SugaredLogger) {
	for {
		select {
		case sm := <-statChan:
			if statSink == nil {
				logger.Error("Stat sink is not connected")
				continue
			}
			if err := statSink.Send(sm); err != nil {
				logger.Errorw("Error while sending stat", zap.Error(err))
			}
		case <-stopCh:
			// It's a sending connection, so no drainage required.
			statSink.Shutdown()
			return
		}
	}
}

func main() {
	flag.Parse()
	cm, err := configmap.Load("/etc/config-logging")
	if err != nil {
		log.Fatal("Error loading logging configuration:", err)
	}
	logConfig, err := logging.NewConfigFromMap(cm)
	if err != nil {
		log.Fatal("Error parsing logging configuration:", err)
	}
	createdLogger, atomicLevel := logging.NewLoggerFromConfig(logConfig, component)
	logger := createdLogger.With(zap.String(logkey.ControllerType, "activator"))
	defer flush(logger)

	logger.Info("Starting the knative activator")

	clusterConfig, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeconfig)
	if err != nil {
		logger.Fatalw("Error getting cluster configuration", zap.Error(err))
	}
	kubeClient, err := kubernetes.NewForConfig(clusterConfig)
	if err != nil {
		logger.Fatalw("Error building new kubernetes client", zap.Error(err))
	}
	servingClient, err := clientset.NewForConfig(clusterConfig)
	if err != nil {
		logger.Fatalw("Error building serving clientset", zap.Error(err))
	}

	// We sometimes startup faster than we can reach kube-api. Poll on failure to prevent us terminating
	if perr := wait.PollImmediate(time.Second, 60*time.Second, func() (bool, error) {
		if err = version.CheckMinimumVersion(kubeClient.Discovery()); err != nil {
			logger.Errorw("Failed to get k8s version", zap.Error(err))
		}
		return err == nil, nil
	}); perr != nil {
		logger.Fatalw("Timed out attempting to get k8s version", zap.Error(err))
	}

	reporter, err := activator.NewStatsReporter()
	if err != nil {
		logger.Fatalw("Failed to create stats reporter", zap.Error(err))
	}

	// Set up signals so we handle the first shutdown signal gracefully.
	stopCh := signals.SetupSignalHandler()
	statChan := make(chan *autoscaler.StatMessage, statReportingQueueLength)
	defer close(statChan)

	reqChan := make(chan activatorhandler.ReqEvent, requestCountingQueueLength)
	defer close(reqChan)

	kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, defaultResyncInterval)
	servingInformerFactory := servinginformers.NewSharedInformerFactory(servingClient, defaultResyncInterval)
	endpointInformer := kubeInformerFactory.Core().V1().Endpoints()
	serviceInformer := kubeInformerFactory.Core().V1().Services()
	revisionInformer := servingInformerFactory.Serving().V1alpha1().Revisions()
	sksInformer := servingInformerFactory.Networking().V1alpha1().ServerlessServices()

	// Run informers instead of starting them from the factory to prevent the sync hanging because of empty handler.
	if err := controller.StartInformers(
		stopCh,
		revisionInformer.Informer(),
		endpointInformer.Informer(),
		serviceInformer.Informer(),
		sksInformer.Informer()); err != nil {
		logger.Fatalw("Failed to start informers", err)
	}

	params := queue.BreakerParams{QueueDepth: breakerQueueDepth, MaxConcurrency: breakerMaxConcurrency, InitialCapacity: 0}

	// Return the number of endpoints, 0 if no endpoints are found.
	endpointsCountGetter := func(sks *nv1a1.ServerlessService) (int, error) {
		count, err := resources.FetchReadyAddressCount(endpointInformer.Lister(), sks.Namespace, sks.Status.PrivateServiceName)
		return count, err
	}

	// Returns the revision from the observer.
	revisionGetter := func(revID activator.RevisionID) (*v1alpha1.Revision, error) {
		return revisionInformer.Lister().Revisions(revID.Namespace).Get(revID.Name)
	}

	// Returns the SKS from the observer.
	sksGetter := func(ns, n string) (*nv1a1.ServerlessService, error) {
		return sksInformer.Lister().ServerlessServices(ns).Get(n)
	}

	serviceGetter := func(namespace, name string) (*corev1.Service, error) {
		return serviceInformer.Lister().Services(namespace).Get(name)
	}

	throttlerParams := activator.ThrottlerParams{
		BreakerParams: params,
		Logger:        logger,
		GetEndpoints:  endpointsCountGetter,
		GetRevision:   revisionGetter,
		GetSKS:        sksGetter,
	}
	throttler := activator.NewThrottler(throttlerParams)

	handler := cache.ResourceEventHandlerFuncs{
		AddFunc:    throttler.UpdateEndpoints,
		UpdateFunc: controller.PassNew(throttler.UpdateEndpoints),
		DeleteFunc: throttler.DeleteBreaker,
	}

	// Update/create the breaker in the throttler when the number of endpoints changes.
	// Pass only the endpoints created by revisions.
	// TODO(greghaynes) we have to allow unset and use the old RevisionUID filter for backwards compat.
	// When we can assume our ServiceTypeKey label is present in all services we can filter all but
	// networking.ServiceTypeKey == networking.ServiceTypePublic
	epFilter := reconciler.ChainFilterFuncs(
		reconciler.LabelExistsFilterFunc(serving.RevisionUID),
		// We are only interested in the private services, since that is
		// what is populated by the actual revision backends.
		reconciler.LabelFilterFunc(networking.ServiceTypeKey, string(networking.ServiceTypePrivate), true),
	)
	endpointInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: epFilter,
		Handler:    handler,
	})

	activatorL3 := fmt.Sprintf("%s:%d", activator.K8sServiceName, activator.ServicePortHTTP1)
	zipkinEndpoint, err := zipkin.NewEndpoint("activator", activatorL3)
	if err != nil {
		logger.Error("Unable to create tracing endpoint")
		return
	}
	oct := tracing.NewOpenCensusTracer(
		tracing.WithZipkinExporter(tracing.CreateZipkinReporter, zipkinEndpoint),
	)
	tracerUpdater := func(name string, value interface{}) {
		if name == tracingconfig.ConfigName {
			cfg := value.(*tracingconfig.Config)
			oct.ApplyConfig(cfg)
		}
	}

	// Set up our config store
	configMapWatcher := configmap.NewInformedWatcher(kubeClient, system.Namespace())
	configStore := activatorconfig.NewStore(createdLogger, tracerUpdater)
	configStore.WatchConfigs(configMapWatcher)

	// Open a websocket connection to the autoscaler
	autoscalerEndpoint := fmt.Sprintf("ws://%s.%s.svc.%s:%d", "autoscaler", system.Namespace(), network.GetClusterDomainName(), autoscalerPort)
	logger.Info("Connecting to autoscaler at", autoscalerEndpoint)
	statSink := websocket.NewDurableSendingConnection(autoscalerEndpoint, logger)
	go statReporter(statSink, stopCh, statChan, logger)

	podName := util.GetRequiredEnvOrFatal("POD_NAME", logger)

	// Create and run our concurrency reporter
	reportTicker := time.NewTicker(time.Second)
	defer reportTicker.Stop()
	cr := activatorhandler.NewConcurrencyReporter(podName, reqChan, reportTicker.C, statChan)
	go cr.Run(stopCh)

	// Create activation handler chain
	// Note: innermost handlers are specified first, ie. the last handler in the chain will be executed first
	var ah http.Handler = &activatorhandler.ActivationHandler{
		Transport:     network.AutoTransport,
		Logger:        logger,
		Reporter:      reporter,
		Throttler:     throttler,
		GetProbeCount: maxRetries,
		GetRevision:   revisionGetter,
		GetSKS:        sksGetter,
		GetService:    serviceGetter,
	}
	ah = activatorhandler.NewRequestEventHandler(reqChan, ah)
	ah = tracing.HTTPSpanMiddleware("handle_request", ah)
	ah = configStore.HTTPMiddleware(ah)
	reqLogHandler, err := pkghttp.NewRequestLogHandler(ah, logging.NewSyncFileWriter(os.Stdout), "",
		requestLogTemplateInputGetter(revisionGetter))
	if err != nil {
		logger.Fatalw("Unable to create request log handler", zap.Error(err))
	}
	ah = reqLogHandler
	ah = &activatorhandler.HealthHandler{HealthCheck: statSink.Status, NextHandler: ah}
	ah = &activatorhandler.ProbeHandler{NextHandler: ah}

	// Watch the logging config map and dynamically update logging levels.
	configMapWatcher.Watch(logging.ConfigMapName(), logging.UpdateLevelFromConfigMap(logger, atomicLevel, component))
	// Watch the observability config map and dynamically update metrics exporter.
	configMapWatcher.Watch(metrics.ObservabilityConfigName, metrics.UpdateExporterFromConfigMap(component, logger))
	// Watch the observability config map and dynamically update request logs.
	configMapWatcher.Watch(metrics.ObservabilityConfigName, updateRequestLogFromConfigMap(logger, reqLogHandler))
	if err = configMapWatcher.Start(stopCh); err != nil {
		logger.Fatalw("Failed to start configuration manager", zap.Error(err))
	}

	http1Srv := network.NewServer(fmt.Sprintf(":%d", networking.BackendHTTPPort), ah)
	go func() {
		if err := http1Srv.ListenAndServe(); err != nil {
			logger.Errorw("Error running HTTP server", zap.Error(err))
		}
	}()

	h2cSrv := network.NewServer(fmt.Sprintf(":%d", networking.BackendHTTP2Port), ah)
	go func() {
		if err := h2cSrv.ListenAndServe(); err != nil {
			logger.Errorw("Error running HTTP server", zap.Error(err))
		}
	}()

	<-stopCh
	http1Srv.Shutdown(context.Background())
	h2cSrv.Shutdown(context.Background())
}

func flush(logger *zap.SugaredLogger) {
	logger.Sync()
	os.Stdout.Sync()
	os.Stderr.Sync()
	pkgmetrics.FlushExporter()
}
<commit_msg>Use health check to be the first handler (#3966)<commit_after>/*
Copyright 2018 The Knative Authors

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

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

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

package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/knative/pkg/configmap"
	"github.com/knative/pkg/controller"
	"github.com/knative/pkg/logging/logkey"
	pkgmetrics "github.com/knative/pkg/metrics"
	"github.com/knative/pkg/signals"
	"github.com/knative/pkg/system"
	"github.com/knative/pkg/version"
	"github.com/knative/pkg/websocket"
	"github.com/knative/serving/cmd/util"
	"github.com/knative/serving/pkg/activator"
	activatorconfig "github.com/knative/serving/pkg/activator/config"
	activatorhandler "github.com/knative/serving/pkg/activator/handler"
	"github.com/knative/serving/pkg/apis/networking"
	nv1a1 "github.com/knative/serving/pkg/apis/networking/v1alpha1"
	"github.com/knative/serving/pkg/apis/serving"
	"github.com/knative/serving/pkg/apis/serving/v1alpha1"
	"github.com/knative/serving/pkg/autoscaler"
	clientset "github.com/knative/serving/pkg/client/clientset/versioned"
	servinginformers "github.com/knative/serving/pkg/client/informers/externalversions"
	"github.com/knative/serving/pkg/goversion"
	pkghttp "github.com/knative/serving/pkg/http"
	"github.com/knative/serving/pkg/logging"
	"github.com/knative/serving/pkg/metrics"
	"github.com/knative/serving/pkg/network"
	"github.com/knative/serving/pkg/queue"
	"github.com/knative/serving/pkg/reconciler"
	"github.com/knative/serving/pkg/resources"
	"github.com/knative/serving/pkg/tracing"
	tracingconfig "github.com/knative/serving/pkg/tracing/config"
	zipkin "github.com/openzipkin/zipkin-go"
	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/wait"
	kubeinformers "k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/clientcmd"
)

// Fail if using unsupported go version.
var _ = goversion.IsSupported()

const (
	component = "activator"

	// This is the number of times we will perform network probes to
	// see if the Revision is accessible before forwarding the actual
	// request.
	maxRetries = 18

	// Add a little buffer space between request handling and stat
	// reporting so that latency in the stat pipeline doesn't
	// interfere with request handling.
	statReportingQueueLength = 10

	// Add enough buffer to not block request serving on stats collection
	requestCountingQueueLength = 100

	// The number of requests that are queued on the breaker before the 503s are sent.
	// The value must be adjusted depending on the actual production requirements.
	breakerQueueDepth = 10000

	// The upper bound for concurrent requests sent to the revision.
	// As new endpoints show up, the Breakers concurrency increases up to this value.
	breakerMaxConcurrency = 1000

	// The port on which autoscaler WebSocket server listens.
	autoscalerPort = 8080

	defaultResyncInterval = 10 * time.Hour
)

var (
	masterURL = flag.String("master", "", "The address of the Kubernetes API server. "+
		"Overrides any value in kubeconfig. Only required if out-of-cluster.")
	kubeconfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
)

func statReporter(statSink *websocket.ManagedConnection, stopCh <-chan struct{},
	statChan <-chan *autoscaler.StatMessage, logger *zap.SugaredLogger) {
	for {
		select {
		case sm := <-statChan:
			if statSink == nil {
				logger.Error("Stat sink is not connected")
				continue
			}
			if err := statSink.Send(sm); err != nil {
				logger.Errorw("Error while sending stat", zap.Error(err))
			}
		case <-stopCh:
			// It's a sending connection, so no drainage required.
			statSink.Shutdown()
			return
		}
	}
}

func main() {
	flag.Parse()
	cm, err := configmap.Load("/etc/config-logging")
	if err != nil {
		log.Fatal("Error loading logging configuration:", err)
	}
	logConfig, err := logging.NewConfigFromMap(cm)
	if err != nil {
		log.Fatal("Error parsing logging configuration:", err)
	}
	createdLogger, atomicLevel := logging.NewLoggerFromConfig(logConfig, component)
	logger := createdLogger.With(zap.String(logkey.ControllerType, "activator"))
	defer flush(logger)

	logger.Info("Starting the knative activator")

	clusterConfig, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeconfig)
	if err != nil {
		logger.Fatalw("Error getting cluster configuration", zap.Error(err))
	}
	kubeClient, err := kubernetes.NewForConfig(clusterConfig)
	if err != nil {
		logger.Fatalw("Error building new kubernetes client", zap.Error(err))
	}
	servingClient, err := clientset.NewForConfig(clusterConfig)
	if err != nil {
		logger.Fatalw("Error building serving clientset", zap.Error(err))
	}

	// We sometimes startup faster than we can reach kube-api. Poll on failure to prevent us terminating
	if perr := wait.PollImmediate(time.Second, 60*time.Second, func() (bool, error) {
		if err = version.CheckMinimumVersion(kubeClient.Discovery()); err != nil {
			logger.Errorw("Failed to get k8s version", zap.Error(err))
		}
		return err == nil, nil
	}); perr != nil {
		logger.Fatalw("Timed out attempting to get k8s version", zap.Error(err))
	}

	reporter, err := activator.NewStatsReporter()
	if err != nil {
		logger.Fatalw("Failed to create stats reporter", zap.Error(err))
	}

	// Set up signals so we handle the first shutdown signal gracefully.
	stopCh := signals.SetupSignalHandler()
	statChan := make(chan *autoscaler.StatMessage, statReportingQueueLength)
	defer close(statChan)

	reqChan := make(chan activatorhandler.ReqEvent, requestCountingQueueLength)
	defer close(reqChan)

	kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, defaultResyncInterval)
	servingInformerFactory := servinginformers.NewSharedInformerFactory(servingClient, defaultResyncInterval)
	endpointInformer := kubeInformerFactory.Core().V1().Endpoints()
	serviceInformer := kubeInformerFactory.Core().V1().Services()
	revisionInformer := servingInformerFactory.Serving().V1alpha1().Revisions()
	sksInformer := servingInformerFactory.Networking().V1alpha1().ServerlessServices()

	// Run informers instead of starting them from the factory to prevent the sync hanging because of empty handler.
	if err := controller.StartInformers(
		stopCh,
		revisionInformer.Informer(),
		endpointInformer.Informer(),
		serviceInformer.Informer(),
		sksInformer.Informer()); err != nil {
		logger.Fatalw("Failed to start informers", err)
	}

	params := queue.BreakerParams{QueueDepth: breakerQueueDepth, MaxConcurrency: breakerMaxConcurrency, InitialCapacity: 0}

	// Return the number of endpoints, 0 if no endpoints are found.
	endpointsCountGetter := func(sks *nv1a1.ServerlessService) (int, error) {
		count, err := resources.FetchReadyAddressCount(endpointInformer.Lister(), sks.Namespace, sks.Status.PrivateServiceName)
		return count, err
	}

	// Returns the revision from the observer.
	revisionGetter := func(revID activator.RevisionID) (*v1alpha1.Revision, error) {
		return revisionInformer.Lister().Revisions(revID.Namespace).Get(revID.Name)
	}

	// Returns the SKS from the observer.
	sksGetter := func(ns, n string) (*nv1a1.ServerlessService, error) {
		return sksInformer.Lister().ServerlessServices(ns).Get(n)
	}

	serviceGetter := func(namespace, name string) (*corev1.Service, error) {
		return serviceInformer.Lister().Services(namespace).Get(name)
	}

	throttlerParams := activator.ThrottlerParams{
		BreakerParams: params,
		Logger:        logger,
		GetEndpoints:  endpointsCountGetter,
		GetRevision:   revisionGetter,
		GetSKS:        sksGetter,
	}
	throttler := activator.NewThrottler(throttlerParams)

	handler := cache.ResourceEventHandlerFuncs{
		AddFunc:    throttler.UpdateEndpoints,
		UpdateFunc: controller.PassNew(throttler.UpdateEndpoints),
		DeleteFunc: throttler.DeleteBreaker,
	}

	// Update/create the breaker in the throttler when the number of endpoints changes.
	// Pass only the endpoints created by revisions.
	// TODO(greghaynes) we have to allow unset and use the old RevisionUID filter for backwards compat.
	// When we can assume our ServiceTypeKey label is present in all services we can filter all but
	// networking.ServiceTypeKey == networking.ServiceTypePublic
	epFilter := reconciler.ChainFilterFuncs(
		reconciler.LabelExistsFilterFunc(serving.RevisionUID),
		// We are only interested in the private services, since that is
		// what is populated by the actual revision backends.
		reconciler.LabelFilterFunc(networking.ServiceTypeKey, string(networking.ServiceTypePrivate), true),
	)
	endpointInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
		FilterFunc: epFilter,
		Handler:    handler,
	})

	activatorL3 := fmt.Sprintf("%s:%d", activator.K8sServiceName, activator.ServicePortHTTP1)
	zipkinEndpoint, err := zipkin.NewEndpoint("activator", activatorL3)
	if err != nil {
		logger.Error("Unable to create tracing endpoint")
		return
	}
	oct := tracing.NewOpenCensusTracer(
		tracing.WithZipkinExporter(tracing.CreateZipkinReporter, zipkinEndpoint),
	)
	tracerUpdater := func(name string, value interface{}) {
		if name == tracingconfig.ConfigName {
			cfg := value.(*tracingconfig.Config)
			oct.ApplyConfig(cfg)
		}
	}

	// Set up our config store
	configMapWatcher := configmap.NewInformedWatcher(kubeClient, system.Namespace())
	configStore := activatorconfig.NewStore(createdLogger, tracerUpdater)
	configStore.WatchConfigs(configMapWatcher)

	// Open a websocket connection to the autoscaler
	autoscalerEndpoint := fmt.Sprintf("ws://%s.%s.svc.%s:%d", "autoscaler", system.Namespace(), network.GetClusterDomainName(), autoscalerPort)
	logger.Info("Connecting to autoscaler at", autoscalerEndpoint)
	statSink := websocket.NewDurableSendingConnection(autoscalerEndpoint, logger)
	go statReporter(statSink, stopCh, statChan, logger)

	podName := util.GetRequiredEnvOrFatal("POD_NAME", logger)

	// Create and run our concurrency reporter
	reportTicker := time.NewTicker(time.Second)
	defer reportTicker.Stop()
	cr := activatorhandler.NewConcurrencyReporter(podName, reqChan, reportTicker.C, statChan)
	go cr.Run(stopCh)

	// Create activation handler chain
	// Note: innermost handlers are specified first, ie. the last handler in the chain will be executed first
	var ah http.Handler = &activatorhandler.ActivationHandler{
		Transport:     network.AutoTransport,
		Logger:        logger,
		Reporter:      reporter,
		Throttler:     throttler,
		GetProbeCount: maxRetries,
		GetRevision:   revisionGetter,
		GetSKS:        sksGetter,
		GetService:    serviceGetter,
	}
	ah = activatorhandler.NewRequestEventHandler(reqChan, ah)
	ah = tracing.HTTPSpanMiddleware("handle_request", ah)
	ah = configStore.HTTPMiddleware(ah)
	reqLogHandler, err := pkghttp.NewRequestLogHandler(ah, logging.NewSyncFileWriter(os.Stdout), "",
		requestLogTemplateInputGetter(revisionGetter))
	if err != nil {
		logger.Fatalw("Unable to create request log handler", zap.Error(err))
	}
	ah = reqLogHandler
	ah = &activatorhandler.ProbeHandler{NextHandler: ah}
	ah = &activatorhandler.HealthHandler{HealthCheck: statSink.Status, NextHandler: ah}

	// Watch the logging config map and dynamically update logging levels.
	configMapWatcher.Watch(logging.ConfigMapName(), logging.UpdateLevelFromConfigMap(logger, atomicLevel, component))
	// Watch the observability config map and dynamically update metrics exporter.
	configMapWatcher.Watch(metrics.ObservabilityConfigName, metrics.UpdateExporterFromConfigMap(component, logger))
	// Watch the observability config map and dynamically update request logs.
	configMapWatcher.Watch(metrics.ObservabilityConfigName, updateRequestLogFromConfigMap(logger, reqLogHandler))
	if err = configMapWatcher.Start(stopCh); err != nil {
		logger.Fatalw("Failed to start configuration manager", zap.Error(err))
	}

	http1Srv := network.NewServer(fmt.Sprintf(":%d", networking.BackendHTTPPort), ah)
	go func() {
		if err := http1Srv.ListenAndServe(); err != nil {
			logger.Errorw("Error running HTTP server", zap.Error(err))
		}
	}()

	h2cSrv := network.NewServer(fmt.Sprintf(":%d", networking.BackendHTTP2Port), ah)
	go func() {
		if err := h2cSrv.ListenAndServe(); err != nil {
			logger.Errorw("Error running HTTP server", zap.Error(err))
		}
	}()

	<-stopCh
	http1Srv.Shutdown(context.Background())
	h2cSrv.Shutdown(context.Background())
}

func flush(logger *zap.SugaredLogger) {
	logger.Sync()
	os.Stdout.Sync()
	os.Stderr.Sync()
	pkgmetrics.FlushExporter()
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Perkeep Authors.

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

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

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

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"perkeep.org/pkg/cmdmain"
	"perkeep.org/pkg/search"

	"go4.org/strutil"
)

type searchCmd struct {
	server   string
	limit    int
	cont     string
	describe bool
	rawQuery bool
}

func init() {
	cmdmain.RegisterCommand("search", func(flags *flag.FlagSet) cmdmain.CommandRunner {
		cmd := new(searchCmd)
		flags.StringVar(&cmd.server, "server", "", "Server to search. "+serverFlagHelp)
		flags.IntVar(&cmd.limit, "limit", 0, "Limit number of results. 0 is default. Negative means no limit.")
		flags.StringVar(&cmd.cont, "continue", "", "Continue token from a previously limited search. The query must be identical to the original search.")
		flags.BoolVar(&cmd.describe, "describe", false, "Describe results as well.")
		flags.BoolVar(&cmd.rawQuery, "rawquery", false, "If true, the provided JSON is a SearchQuery, and not a Constraint. In this case, the -limit and -continue flags, if non-zero, are applied after parsing the JSON.")
		return cmd
	})
}

func (c *searchCmd) Describe() string {
	return "Execute a search query"
}

func (c *searchCmd) Usage() {
	fmt.Fprintf(os.Stderr, "Usage: camtool [globalopts] search <expr or Constraint JSON>\n")
}

func (c *searchCmd) Examples() []string {
	return []string{
		`"loc:paris is:portrait" # expression`,
		`'{"blobrefPrefix":"sha1-f00d"}' # SearchConstraint JSON`,
		`- # piped from stdin`,
	}
}

func (c *searchCmd) RunCommand(args []string) error {
	if len(args) != 1 {
		return cmdmain.UsageError("requires search expression or Constraint JSON")
	}
	q := args[0]
	if q == "-" {
		slurp, err := ioutil.ReadAll(cmdmain.Stdin)
		if err != nil {
			return err
		}
		q = string(slurp)
	}
	q = strings.TrimSpace(q)

	req := &search.SearchQuery{
		Limit:    c.limit,
		Continue: c.cont,
	}
	if c.rawQuery {
		req.Limit = 0     // clear it if they provided it
		req.Continue = "" // clear this as well

		if err := json.NewDecoder(strings.NewReader(q)).Decode(&req); err != nil {
			return err
		}
		if c.limit != 0 {
			req.Limit = c.limit
		}
		if c.cont != "" {
			req.Continue = c.cont
		}
	} else if strutil.IsPlausibleJSON(q) {
		cs := new(search.Constraint)
		if err := json.NewDecoder(strings.NewReader(q)).Decode(&cs); err != nil {
			return err
		}
		req.Constraint = cs
	} else {
		req.Expression = q
	}
	if c.describe {
		req.Describe = &search.DescribeRequest{}
	}

	cl := newClient(c.server)
	res, err := cl.Query(ctxbg, req)
	if err != nil {
		return err
	}
	resj, err := json.MarshalIndent(res, "", "  ")
	if err != nil {
		return err
	}
	resj = append(resj, '\n')
	_, err = os.Stdout.Write(resj)
	return err
}
<commit_msg>cmd/camtool: add "-1" flag to search subcommand<commit_after>/*
Copyright 2014 The Perkeep Authors.

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

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

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

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"perkeep.org/pkg/cmdmain"
	"perkeep.org/pkg/search"

	"go4.org/strutil"
)

type searchCmd struct {
	server   string
	limit    int
	cont     string
	describe bool
	rawQuery bool
	one      bool
}

func init() {
	cmdmain.RegisterCommand("search", func(flags *flag.FlagSet) cmdmain.CommandRunner {
		cmd := new(searchCmd)
		flags.StringVar(&cmd.server, "server", "", "Server to search. "+serverFlagHelp)
		flags.IntVar(&cmd.limit, "limit", 0, "Limit number of results. 0 is default. Negative means no limit.")
		flags.StringVar(&cmd.cont, "continue", "", "Continue token from a previously limited search. The query must be identical to the original search.")
		flags.BoolVar(&cmd.describe, "describe", false, "Describe results as well.")
		flags.BoolVar(&cmd.rawQuery, "rawquery", false, "If true, the provided JSON is a SearchQuery, and not a Constraint. In this case, the -limit and -continue flags, if non-zero, are applied after parsing the JSON.")
		flags.BoolVar(&cmd.one, "1", false, "Output one blob ref per line, suitable for piping into xargs.")
		return cmd
	})
}

func (c *searchCmd) Describe() string {
	return "Execute a search query"
}

func (c *searchCmd) Usage() {
	fmt.Fprintf(os.Stderr, "Usage: camtool [globalopts] search <expr or Constraint JSON>\n")
}

func (c *searchCmd) Examples() []string {
	return []string{
		`"loc:paris is:portrait" # expression`,
		`'{"blobrefPrefix":"sha1-f00d"}' # SearchConstraint JSON`,
		`- # piped from stdin`,
	}
}

func (c *searchCmd) RunCommand(args []string) error {
	if len(args) != 1 {
		return cmdmain.UsageError("requires search expression or Constraint JSON")
	}
	q := args[0]
	if q == "-" {
		slurp, err := ioutil.ReadAll(cmdmain.Stdin)
		if err != nil {
			return err
		}
		q = string(slurp)
	}
	q = strings.TrimSpace(q)

	req := &search.SearchQuery{
		Limit:    c.limit,
		Continue: c.cont,
	}
	if c.rawQuery {
		req.Limit = 0     // clear it if they provided it
		req.Continue = "" // clear this as well

		if err := json.NewDecoder(strings.NewReader(q)).Decode(&req); err != nil {
			return err
		}
		if c.limit != 0 {
			req.Limit = c.limit
		}
		if c.cont != "" {
			req.Continue = c.cont
		}
	} else if strutil.IsPlausibleJSON(q) {
		cs := new(search.Constraint)
		if err := json.NewDecoder(strings.NewReader(q)).Decode(&cs); err != nil {
			return err
		}
		req.Constraint = cs
	} else {
		req.Expression = q
	}
	if c.describe {
		req.Describe = &search.DescribeRequest{}
	}

	cl := newClient(c.server)
	res, err := cl.Query(ctxbg, req)
	if err != nil {
		return err
	}
	if c.one {
		for _, bl := range res.Blobs {
			fmt.Println(bl.Blob)
		}
		return nil
	}
	resj, err := json.MarshalIndent(res, "", "  ")
	if err != nil {
		return err
	}
	resj = append(resj, '\n')
	_, err = os.Stdout.Write(resj)
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"runtime"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	raven "github.com/getsentry/raven-go"
	"github.com/honeycombio/beeline-go"
	libhoney "github.com/honeycombio/libhoney-go"
	librato "github.com/mihasya/go-metrics-librato"
	metrics "github.com/rcrowley/go-metrics"
	travismetrics "github.com/travis-ci/jupiter-brain/metrics"
	"github.com/travis-ci/jupiter-brain/server"
)

func main() {
	app := cli.NewApp()
	app.Usage = "Jupiter Brain API server"
	app.Author = "Travis CI"
	app.Email = "contact+jupiter-brain@travis-ci.org"
	app.Version = VersionString
	app.Compiled = GeneratedTime()
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "addr",
			Usage: "host:port to listen to",
			Value: func() string {
				v := ":" + os.Getenv("PORT")
				if v == ":" {
					v = ":42161"
				}
				return v
			}(),
			EnvVar: "JUPITER_BRAIN_ADDR",
		},
		cli.StringFlag{
			Name:   "auth-token",
			Usage:  "authentication token for the api server",
			EnvVar: "JUPITER_BRAIN_AUTH_TOKEN",
		},
		cli.StringFlag{
			Name:   "vsphere-api-url",
			Usage:  "URL to vSphere API",
			EnvVar: "JUPITER_BRAIN_VSPHERE_API_URL,VSPHERE_API_URL",
		},
		cli.StringFlag{
			Name:   "vsphere-base-path",
			Usage:  "path to folder of base VMs in vSphere inventory",
			EnvVar: "JUPITER_BRAIN_VSPHERE_BASE_PATH",
		},
		cli.StringFlag{
			Name:   "vsphere-vm-path",
			Usage:  "path to folder where VMs will be put in vSphere inventory",
			EnvVar: "JUPITER_BRAIN_VSPHERE_VM_PATH",
		},
		cli.StringFlag{
			Name:   "vsphere-cluster-path",
			Usage:  "path to compute cluster that VMs will be booted in",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CLUSTER_PATH",
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-read-operations",
			Usage:  "number of concurrent fetch and list operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_READ_OPERATIONS",
			Value:  4,
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-create-operations",
			Usage:  "number of concurrent start operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_CREATE_OPERATIONS",
			Value:  48,
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-delete-operations",
			Usage:  "number of concurrent terminate operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_DELETE_OPERATIONS",
			Value:  48,
		},
		cli.StringFlag{
			Name:   "database-url",
			Usage:  "URL to the PostgreSQL database",
			EnvVar: "JUPITER_BRAIN_DATABASE_URL,DATABASE_URL",
		},
		cli.IntFlag{
			Name:   "database-pool-size",
			Usage:  "pool size",
			EnvVar: "JUPITER_BRAIN_DATABASE_POOL_SIZE,DATABASE_POOL_SIZE",
			Value:  2,
		},
		cli.BoolFlag{
			Name:   "debug",
			Usage:  "enable debug logging",
			EnvVar: "JUPITER_BRAIN_DEBUG,DEBUG",
		},
		cli.StringFlag{
			Name:   "sentry-dsn",
			Usage:  "Sentry DSN to send errors to",
			EnvVar: "JUPITER_BRAIN_SENTRY_DSN,SENTRY_DSN",
		},
		cli.StringFlag{
			Name:   "sentry-environment",
			Usage:  "Environment name to pass to Sentry",
			EnvVar: "JUPITER_BRAIN_SENTRY_ENVIRONMENT,SENTRY_ENVIRONMENT",
		},
		cli.StringFlag{
			Name:   "librato-email",
			Usage:  "Email for Librato account to send metrics to",
			EnvVar: "JUPITER_BRAIN_LIBRATO_EMAIL,LIBRATO_EMAIL",
		},
		cli.StringFlag{
			Name:   "librato-token",
			Usage:  "Token for Librato account to send metrics to",
			EnvVar: "JUPITER_BRAIN_LIBRATO_TOKEN,LIBRATO_TOKEN",
		},
		cli.StringFlag{
			Name:   "librato-source",
			Usage:  "The source to use when sending metrics to Librato",
			EnvVar: "JUPITER_BRAIN_LIBRATO_SOURCE,LIBRATO_SOURCE",
		},
		cli.StringFlag{
			Name:   "pprof-addr",
			Usage:  "Whether to enable pprof endpoints over HTTP",
			EnvVar: "JUPITER_BRAIN_PPROF_ADDR",
		},
		cli.DurationFlag{
			Name:   "request-timeout",
			Usage:  "The max time an incoming HTTP request can take before timing out",
			EnvVar: "JUPITER_BRAIN_REQUEST_TIMEOUT",
			Value:  3 * time.Minute,
		},
		cli.StringFlag{
			Name:   "honeycomb-write-key",
			Usage:  "The write key for Honeycomb",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_WRITE_KEY",
		},
		cli.StringFlag{
			Name:   "honeycomb-dataset",
			Usage:  "The dataset name for Honeycomb",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_DATASET",
		},
		cli.StringFlag{
			Name:   "honeycomb-request-dataset",
			Usage:  "The dataset name for Honeycomb to track HTTP requests",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_REQUEST_DATASET",
		},
	}
	app.Action = runServer

	app.RunAndExitOnError()
}

func runServer(c *cli.Context) {
	logrus.SetFormatter(&logrus.TextFormatter{DisableColors: true})

	if c.String("librato-email") != "" && c.String("librato-token") != "" && c.String("librato-source") != "" {
		logrus.Info("starting librato metrics reporter")

		go librato.Librato(
			metrics.DefaultRegistry,
			time.Minute,
			c.String("librato-email"),
			c.String("librato-token"),
			c.String("librato-source"),
			[]float64{0.50, 0.75, 0.90, 0.95, 0.99, 0.999, 1.0},
			time.Millisecond,
		)
	}
	go travismetrics.ReportMemstatsMetrics()

	if c.String("honeycomb-write-key") != "" && c.String("honeycomb-dataset") != "" {
		if c.String("honeycomb-dataset") != "" {
			libhoney.Init(libhoney.Config{
				WriteKey: c.String("honeycomb-write-key"),
				Dataset:  c.String("honeycomb-dataset"),
			})
			defer libhoney.Close()

			libhoney.AddDynamicField("num_goroutines", func() interface{} { return runtime.NumGoroutine() })
			libhoney.AddField("jupiter_brain_version", c.App.Version)
			libhoney.AddField("jupiter_brain_source", c.String("librato-source"))
		}

		if c.String("honeycomb-request-dataset") != "" {
			beeline.Init(beeline.Config{
				WriteKey: c.String("honeycomb-write-key"),
				Dataset:  c.String("honeycomb-request-dataset"),
			})
		}
	}

	raven.SetDSN(c.String("sentry-dsn"))
	raven.SetRelease(VersionString)
	if c.String("sentry-environment") != "" {
		raven.SetEnvironment(c.String("sentry-environment"))
	}

	server.Main(&server.Config{
		Addr:              c.String("addr"),
		AuthToken:         c.String("auth-token"),
		Debug:             c.Bool("debug"),
		SentryDSN:         c.String("sentry-dsn"),
		SentryEnvironment: c.String("sentry-environment"),

		VSphereURL:                        c.String("vsphere-api-url"),
		VSphereBasePath:                   c.String("vsphere-base-path"),
		VSphereVMPath:                     c.String("vsphere-vm-path"),
		VSphereClusterPath:                c.String("vsphere-cluster-path"),
		VSphereConcurrentReadOperations:   c.Int("vsphere-concurrent-read-operations"),
		VSphereConcurrentCreateOperations: c.Int("vsphere-concurrent-create-operations"),
		VSphereConcurrentDeleteOperations: c.Int("vsphere-concurrent-delete-operations"),

		DatabaseURL:      c.String("database-url"),
		DatabasePoolSize: c.Int("database-pool-size"),

		PprofAddr:      c.String("pprof-addr"),
		RequestTimeout: c.Duration("request-timeout"),
	})
}
<commit_msg>Fix Honeycomb check for only using request dataset<commit_after>package main

import (
	"os"
	"runtime"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	raven "github.com/getsentry/raven-go"
	"github.com/honeycombio/beeline-go"
	libhoney "github.com/honeycombio/libhoney-go"
	librato "github.com/mihasya/go-metrics-librato"
	metrics "github.com/rcrowley/go-metrics"
	travismetrics "github.com/travis-ci/jupiter-brain/metrics"
	"github.com/travis-ci/jupiter-brain/server"
)

func main() {
	app := cli.NewApp()
	app.Usage = "Jupiter Brain API server"
	app.Author = "Travis CI"
	app.Email = "contact+jupiter-brain@travis-ci.org"
	app.Version = VersionString
	app.Compiled = GeneratedTime()
	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "addr",
			Usage: "host:port to listen to",
			Value: func() string {
				v := ":" + os.Getenv("PORT")
				if v == ":" {
					v = ":42161"
				}
				return v
			}(),
			EnvVar: "JUPITER_BRAIN_ADDR",
		},
		cli.StringFlag{
			Name:   "auth-token",
			Usage:  "authentication token for the api server",
			EnvVar: "JUPITER_BRAIN_AUTH_TOKEN",
		},
		cli.StringFlag{
			Name:   "vsphere-api-url",
			Usage:  "URL to vSphere API",
			EnvVar: "JUPITER_BRAIN_VSPHERE_API_URL,VSPHERE_API_URL",
		},
		cli.StringFlag{
			Name:   "vsphere-base-path",
			Usage:  "path to folder of base VMs in vSphere inventory",
			EnvVar: "JUPITER_BRAIN_VSPHERE_BASE_PATH",
		},
		cli.StringFlag{
			Name:   "vsphere-vm-path",
			Usage:  "path to folder where VMs will be put in vSphere inventory",
			EnvVar: "JUPITER_BRAIN_VSPHERE_VM_PATH",
		},
		cli.StringFlag{
			Name:   "vsphere-cluster-path",
			Usage:  "path to compute cluster that VMs will be booted in",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CLUSTER_PATH",
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-read-operations",
			Usage:  "number of concurrent fetch and list operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_READ_OPERATIONS",
			Value:  4,
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-create-operations",
			Usage:  "number of concurrent start operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_CREATE_OPERATIONS",
			Value:  48,
		},
		cli.IntFlag{
			Name:   "vsphere-concurrent-delete-operations",
			Usage:  "number of concurrent terminate operations",
			EnvVar: "JUPITER_BRAIN_VSPHERE_CONCURRENT_DELETE_OPERATIONS",
			Value:  48,
		},
		cli.StringFlag{
			Name:   "database-url",
			Usage:  "URL to the PostgreSQL database",
			EnvVar: "JUPITER_BRAIN_DATABASE_URL,DATABASE_URL",
		},
		cli.IntFlag{
			Name:   "database-pool-size",
			Usage:  "pool size",
			EnvVar: "JUPITER_BRAIN_DATABASE_POOL_SIZE,DATABASE_POOL_SIZE",
			Value:  2,
		},
		cli.BoolFlag{
			Name:   "debug",
			Usage:  "enable debug logging",
			EnvVar: "JUPITER_BRAIN_DEBUG,DEBUG",
		},
		cli.StringFlag{
			Name:   "sentry-dsn",
			Usage:  "Sentry DSN to send errors to",
			EnvVar: "JUPITER_BRAIN_SENTRY_DSN,SENTRY_DSN",
		},
		cli.StringFlag{
			Name:   "sentry-environment",
			Usage:  "Environment name to pass to Sentry",
			EnvVar: "JUPITER_BRAIN_SENTRY_ENVIRONMENT,SENTRY_ENVIRONMENT",
		},
		cli.StringFlag{
			Name:   "librato-email",
			Usage:  "Email for Librato account to send metrics to",
			EnvVar: "JUPITER_BRAIN_LIBRATO_EMAIL,LIBRATO_EMAIL",
		},
		cli.StringFlag{
			Name:   "librato-token",
			Usage:  "Token for Librato account to send metrics to",
			EnvVar: "JUPITER_BRAIN_LIBRATO_TOKEN,LIBRATO_TOKEN",
		},
		cli.StringFlag{
			Name:   "librato-source",
			Usage:  "The source to use when sending metrics to Librato",
			EnvVar: "JUPITER_BRAIN_LIBRATO_SOURCE,LIBRATO_SOURCE",
		},
		cli.StringFlag{
			Name:   "pprof-addr",
			Usage:  "Whether to enable pprof endpoints over HTTP",
			EnvVar: "JUPITER_BRAIN_PPROF_ADDR",
		},
		cli.DurationFlag{
			Name:   "request-timeout",
			Usage:  "The max time an incoming HTTP request can take before timing out",
			EnvVar: "JUPITER_BRAIN_REQUEST_TIMEOUT",
			Value:  3 * time.Minute,
		},
		cli.StringFlag{
			Name:   "honeycomb-write-key",
			Usage:  "The write key for Honeycomb",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_WRITE_KEY",
		},
		cli.StringFlag{
			Name:   "honeycomb-dataset",
			Usage:  "The dataset name for Honeycomb",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_DATASET",
		},
		cli.StringFlag{
			Name:   "honeycomb-request-dataset",
			Usage:  "The dataset name for Honeycomb to track HTTP requests",
			EnvVar: "JUPITER_BRAIN_HONEYCOMB_REQUEST_DATASET",
		},
	}
	app.Action = runServer

	app.RunAndExitOnError()
}

func runServer(c *cli.Context) {
	logrus.SetFormatter(&logrus.TextFormatter{DisableColors: true})

	if c.String("librato-email") != "" && c.String("librato-token") != "" && c.String("librato-source") != "" {
		logrus.Info("starting librato metrics reporter")

		go librato.Librato(
			metrics.DefaultRegistry,
			time.Minute,
			c.String("librato-email"),
			c.String("librato-token"),
			c.String("librato-source"),
			[]float64{0.50, 0.75, 0.90, 0.95, 0.99, 0.999, 1.0},
			time.Millisecond,
		)
	}
	go travismetrics.ReportMemstatsMetrics()

	if c.String("honeycomb-write-key") != "" {
		if c.String("honeycomb-dataset") != "" {
			libhoney.Init(libhoney.Config{
				WriteKey: c.String("honeycomb-write-key"),
				Dataset:  c.String("honeycomb-dataset"),
			})
			defer libhoney.Close()

			libhoney.AddDynamicField("num_goroutines", func() interface{} { return runtime.NumGoroutine() })
			libhoney.AddField("jupiter_brain_version", c.App.Version)
			libhoney.AddField("jupiter_brain_source", c.String("librato-source"))
		}

		if c.String("honeycomb-request-dataset") != "" {
			beeline.Init(beeline.Config{
				WriteKey: c.String("honeycomb-write-key"),
				Dataset:  c.String("honeycomb-request-dataset"),
			})
		}
	}

	raven.SetDSN(c.String("sentry-dsn"))
	raven.SetRelease(VersionString)
	if c.String("sentry-environment") != "" {
		raven.SetEnvironment(c.String("sentry-environment"))
	}

	server.Main(&server.Config{
		Addr:              c.String("addr"),
		AuthToken:         c.String("auth-token"),
		Debug:             c.Bool("debug"),
		SentryDSN:         c.String("sentry-dsn"),
		SentryEnvironment: c.String("sentry-environment"),

		VSphereURL:                        c.String("vsphere-api-url"),
		VSphereBasePath:                   c.String("vsphere-base-path"),
		VSphereVMPath:                     c.String("vsphere-vm-path"),
		VSphereClusterPath:                c.String("vsphere-cluster-path"),
		VSphereConcurrentReadOperations:   c.Int("vsphere-concurrent-read-operations"),
		VSphereConcurrentCreateOperations: c.Int("vsphere-concurrent-create-operations"),
		VSphereConcurrentDeleteOperations: c.Int("vsphere-concurrent-delete-operations"),

		DatabaseURL:      c.String("database-url"),
		DatabasePoolSize: c.Int("database-pool-size"),

		PprofAddr:      c.String("pprof-addr"),
		RequestTimeout: c.Duration("request-timeout"),
	})
}
<|endoftext|>


<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package main

import (
	"github.com/juju/cmd"
	"github.com/juju/juju/testing/factory"
	"github.com/juju/names"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	jujutesting "github.com/juju/juju/juju/testing"
	"github.com/juju/juju/testing"
)

// UserSuite tests the connectivity of all the user subcommands. These tests
// go from the command line, api client, api server, db. The db changes are
// then checked.  Only one test for each command is done here to check
// connectivity.  Exhaustive tests are at each layer.
type UserSuite struct {
	jujutesting.JujuConnSuite
}

var _ = gc.Suite(&UserSuite{})

func (s *UserSuite) RunUserCommand(c *gc.C, commands ...string) (*cmd.Context, error) {
	args := []string{"user"}
	args = append(args, commands...)
	context := testing.Context(c)
	juju := NewJujuCommand(context)
	if err := testing.InitCommand(juju, args); err != nil {
		return context, err
	}
	return context, juju.Run(context)
}

func (s *UserSuite) TestUserAdd(c *gc.C) {
	ctx, err := s.RunUserCommand(c, "add", "test", "--generate")
	c.Assert(err, gc.IsNil)
	c.Assert(testing.Stdout(ctx), jc.HasPrefix, `user "test" added`)
	user, err := s.State.User(names.NewLocalUserTag("test"))
	c.Assert(err, gc.IsNil)
	c.Assert(user.IsDisabled(), jc.IsFalse)
}

func (s *UserSuite) TestUserChangePassword(c *gc.C) {
	user, err := s.State.User(s.AdminUserTag(c))
	c.Assert(err, gc.IsNil)
	c.Assert(user.PasswordValid("dummy-secret"), jc.IsTrue)
	_, err = s.RunUserCommand(c, "change-password", "--generate")
	c.Assert(err, gc.IsNil)
	user.Refresh()
	c.Assert(err, gc.IsNil)
	c.Assert(user.PasswordValid("dummy-secret"), jc.IsFalse)
}

func (s *UserSuite) TestUserInfo(c *gc.C) {
	user, err := s.State.User(s.AdminUserTag(c))
	c.Assert(err, gc.IsNil)
	c.Assert(user.PasswordValid("dummy-secret"), jc.IsTrue)
	ctx, err := s.RunUserCommand(c, "info")
	c.Assert(err, gc.IsNil)
	c.Assert(testing.Stdout(ctx), jc.Contains, "user-name: dummy-admin")
}

func (s *UserSuite) TestUserDisable(c *gc.C) {
	user := s.Factory.MakeUser(c, &factory.UserParams{Name: "barbara"})
	_, err := s.RunUserCommand(c, "disable", "barbara")
	c.Assert(err, gc.IsNil)
	user.Refresh()
	c.Assert(err, gc.IsNil)
	c.Assert(user.IsDisabled(), jc.IsTrue)
}

func (s *UserSuite) TestUserEnable(c *gc.C) {
	user := s.Factory.MakeUser(c, &factory.UserParams{Name: "barbara", Disabled: true})
	_, err := s.RunUserCommand(c, "enable", "barbara")
	c.Assert(err, gc.IsNil)
	user.Refresh()
	c.Assert(err, gc.IsNil)
	c.Assert(user.IsDisabled(), jc.IsFalse)
}

func (s *UserSuite) TestUserList(c *gc.C) {
	ctx, err := s.RunUserCommand(c, "list")
	c.Assert(err, gc.IsNil)
	expected := `
NAME         DISPLAY NAME  DATE CREATED  LAST CONNECTION
dummy-admin  dummy-admin   just now      just now

`[1:]
	c.Assert(testing.Stdout(ctx), gc.Equals, expected)
}
<commit_msg>cmd/juju: user list test is now flexible about durations<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package main

import (
	"fmt"

	"github.com/juju/cmd"
	"github.com/juju/juju/testing/factory"
	"github.com/juju/names"
	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"

	jujutesting "github.com/juju/juju/juju/testing"
	"github.com/juju/juju/testing"
)

// UserSuite tests the connectivity of all the user subcommands. These tests
// go from the command line, api client, api server, db. The db changes are
// then checked.  Only one test for each command is done here to check
// connectivity.  Exhaustive tests are at each layer.
type UserSuite struct {
	jujutesting.JujuConnSuite
}

var _ = gc.Suite(&UserSuite{})

func (s *UserSuite) RunUserCommand(c *gc.C, commands ...string) (*cmd.Context, error) {
	args := []string{"user"}
	args = append(args, commands...)
	context := testing.Context(c)
	juju := NewJujuCommand(context)
	if err := testing.InitCommand(juju, args); err != nil {
		return context, err
	}
	return context, juju.Run(context)
}

func (s *UserSuite) TestUserAdd(c *gc.C) {
	ctx, err := s.RunUserCommand(c, "add", "test", "--generate")
	c.Assert(err, gc.IsNil)
	c.Assert(testing.Stdout(ctx), jc.HasPrefix, `user "test" added`)
	user, err := s.State.User(names.NewLocalUserTag("test"))
	c.Assert(err, gc.IsNil)
	c.Assert(user.IsDisabled(), jc.IsFalse)
}

func (s *UserSuite) TestUserChangePassword(c *gc.C) {
	user, err := s.State.User(s.AdminUserTag(c))
	c.Assert(err, gc.IsNil)
	c.Assert(user.PasswordValid("dummy-secret"), jc.IsTrue)
	_, err = s.RunUserCommand(c, "change-password", "--generate")
	c.Assert(err, gc.IsNil)
	user.Refresh()
	c.Assert(err, gc.IsNil)
	c.Assert(user.PasswordValid("dummy-secret"), jc.IsFalse)
}

func (s *UserSuite) TestUserInfo(c *gc.C) {
	user, err := s.State.User(s.AdminUserTag(c))
	c.Assert(err, gc.IsNil)
	c.Assert(user.PasswordValid("dummy-secret"), jc.IsTrue)
	ctx, err := s.RunUserCommand(c, "info")
	c.Assert(err, gc.IsNil)
	c.Assert(testing.Stdout(ctx), jc.Contains, "user-name: dummy-admin")
}

func (s *UserSuite) TestUserDisable(c *gc.C) {
	user := s.Factory.MakeUser(c, &factory.UserParams{Name: "barbara"})
	_, err := s.RunUserCommand(c, "disable", "barbara")
	c.Assert(err, gc.IsNil)
	user.Refresh()
	c.Assert(err, gc.IsNil)
	c.Assert(user.IsDisabled(), jc.IsTrue)
}

func (s *UserSuite) TestUserEnable(c *gc.C) {
	user := s.Factory.MakeUser(c, &factory.UserParams{Name: "barbara", Disabled: true})
	_, err := s.RunUserCommand(c, "enable", "barbara")
	c.Assert(err, gc.IsNil)
	user.Refresh()
	c.Assert(err, gc.IsNil)
	c.Assert(user.IsDisabled(), jc.IsFalse)
}

func (s *UserSuite) TestUserList(c *gc.C) {
	ctx, err := s.RunUserCommand(c, "list")
	c.Assert(err, gc.IsNil)
	periodPattern := `(just now|\d+ \S+ ago)`
	expected := fmt.Sprintf(`
NAME\s+DISPLAY NAME\s+DATE CREATED\s+LAST CONNECTION
dummy-admin\s+dummy-admin\s+%s\s+%s

`[1:], periodPattern, periodPattern)
	c.Assert(testing.Stdout(ctx), gc.Matches, expected)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"time"
)

var (
	options struct {
		id                     string
		zone                   string
		configFile             string
		pubHttpAddr            string
		pubHttpsAddr           string
		subHttpAddr            string
		subHttpsAddr           string
		manHttpAddr            string
		manHttpsAddr           string
		store                  string
		pidFile                string
		debugHttpAddr          string
		certFile               string
		keyFile                string
		logFile                string
		logLevel               string
		crashLogFile           string
		influxServer           string
		killFile               string
		showVersion            bool
		ratelimit              bool
		disableMetrics         bool
		dryRun                 bool
		clusterAware           bool
		cpuAffinity            bool
		golangTrace            bool
		debug                  bool
		maxPubSize             int64
		minPubSize             int
		maxPubRetries          int
		maxClients             int
		pubPoolCapcity         int
		pubPoolIdleTimeout     time.Duration
		subTimeout             time.Duration
		offsetCommitInterval   time.Duration
		reporterInterval       time.Duration
		consoleMetricsInterval time.Duration
		metaRefresh            time.Duration
		httpReadTimeout        time.Duration
		httpWriteTimeout       time.Duration
	}
)

func parseFlags() {
	flag.StringVar(&options.id, "id", "", "kateway id, the id must be unique within a host")
	flag.StringVar(&options.zone, "zone", "", "kafka zone name")
	flag.DurationVar(&options.metaRefresh, "metarefresh", time.Minute*10, "meta data refresh interval")
	flag.StringVar(&options.pubHttpAddr, "pubhttp", ":9191", "pub http bind addr")
	flag.StringVar(&options.pubHttpsAddr, "pubhttps", "", "pub https bind addr")
	flag.StringVar(&options.subHttpAddr, "subhttp", ":9192", "sub http bind addr")
	flag.StringVar(&options.subHttpsAddr, "subhttps", "", "sub https bind addr")
	flag.StringVar(&options.manHttpAddr, "manhttp", ":9193", "management http bind addr")
	flag.StringVar(&options.manHttpsAddr, "manhttps", "", "management https bind addr")
	flag.StringVar(&options.logLevel, "level", "debug", "log level")
	flag.StringVar(&options.logFile, "log", "stdout", "log file, default stdout")
	flag.StringVar(&options.crashLogFile, "crashlog", "", "crash log")
	flag.StringVar(&options.certFile, "certfile", "", "cert file path")
	flag.StringVar(&options.pidFile, "pid", "", "pid file")
	flag.StringVar(&options.keyFile, "keyfile", "", "key file path")
	flag.StringVar(&options.debugHttpAddr, "debughttp", "", "debug http bind addr")
	flag.StringVar(&options.store, "store", "kafka", "backend store")
	flag.StringVar(&options.configFile, "conf", "/etc/kateway.cf", "config file")
	flag.BoolVar(&options.debug, "debug", false, "enable debug mode")
	flag.BoolVar(&options.golangTrace, "gotrace", false, "go tool trace")
	flag.StringVar(&options.killFile, "kill", "", "kill running kateway by pid file")
	flag.BoolVar(&options.showVersion, "version", false, "show version and exit")
	flag.BoolVar(&options.dryRun, "dryrun", false, "dry run mode")
	flag.BoolVar(&options.cpuAffinity, "cpuaffinity", false, "enable cpu affinity")
	flag.BoolVar(&options.ratelimit, "raltelimit", false, "enable rate limit")
	flag.BoolVar(&options.clusterAware, "clusteraware", false, "each kateway knows the cluster nodes")
	flag.BoolVar(&options.disableMetrics, "metricsoff", false, "disable metrics reporter")
	flag.StringVar(&options.influxServer, "influxdb", "http://10.77.144.193:10036", "influxdb server address for the metrics reporter")
	flag.DurationVar(&options.reporterInterval, "report", time.Second*10, "reporter flush interval")
	flag.Int64Var(&options.maxPubSize, "maxpub", 1<<20, "max Pub message size")
	flag.IntVar(&options.minPubSize, "minpub", 1, "min Pub message size")
	flag.IntVar(&options.maxPubRetries, "pubretry", 5, "max retries when Pub fails")
	flag.IntVar(&options.pubPoolCapcity, "pubpool", 100, "pub connection pool capacity")
	flag.IntVar(&options.maxClients, "maxclient", 100000, "max concurrent connections")
	flag.DurationVar(&options.offsetCommitInterval, "offsetcommit", time.Minute, "consumer offset commit interval")
	flag.DurationVar(&options.httpReadTimeout, "httprtimeout", time.Second*60, "http server read timeout")
	flag.DurationVar(&options.httpWriteTimeout, "httpwtimeout", time.Second*60, "http server write timeout")
	flag.DurationVar(&options.subTimeout, "subtimeout", time.Minute, "sub timeout before send http 204")
	flag.DurationVar(&options.consoleMetricsInterval, "consolemetrics", 0, "console metrics report interval")
	flag.DurationVar(&options.pubPoolIdleTimeout, "pubpoolidle", 0, "pub pool connect idle timeout")

	flag.Parse()
}

func validateFlags() {
	if options.killFile != "" {
		return
	}

	if options.zone == "" {
		fmt.Fprintf(os.Stderr, "-zone required\n")
		os.Exit(1)
	}

	if options.manHttpsAddr == "" && options.manHttpAddr == "" {
		fmt.Fprintf(os.Stderr, "-manhttp or -manhttps required\n")
	}
}
<commit_msg>default meta refresh interval: 1h<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"time"
)

var (
	options struct {
		id                     string
		zone                   string
		configFile             string
		pubHttpAddr            string
		pubHttpsAddr           string
		subHttpAddr            string
		subHttpsAddr           string
		manHttpAddr            string
		manHttpsAddr           string
		store                  string
		pidFile                string
		debugHttpAddr          string
		certFile               string
		keyFile                string
		logFile                string
		logLevel               string
		crashLogFile           string
		influxServer           string
		killFile               string
		showVersion            bool
		ratelimit              bool
		disableMetrics         bool
		dryRun                 bool
		clusterAware           bool
		cpuAffinity            bool
		golangTrace            bool
		debug                  bool
		maxPubSize             int64
		minPubSize             int
		maxPubRetries          int
		maxClients             int
		pubPoolCapcity         int
		pubPoolIdleTimeout     time.Duration
		subTimeout             time.Duration
		offsetCommitInterval   time.Duration
		reporterInterval       time.Duration
		consoleMetricsInterval time.Duration
		metaRefresh            time.Duration
		httpReadTimeout        time.Duration
		httpWriteTimeout       time.Duration
	}
)

func parseFlags() {
	flag.StringVar(&options.id, "id", "", "kateway id, the id must be unique within a host")
	flag.StringVar(&options.zone, "zone", "", "kafka zone name")
	flag.StringVar(&options.pubHttpAddr, "pubhttp", ":9191", "pub http bind addr")
	flag.StringVar(&options.pubHttpsAddr, "pubhttps", "", "pub https bind addr")
	flag.StringVar(&options.subHttpAddr, "subhttp", ":9192", "sub http bind addr")
	flag.StringVar(&options.subHttpsAddr, "subhttps", "", "sub https bind addr")
	flag.StringVar(&options.manHttpAddr, "manhttp", ":9193", "management http bind addr")
	flag.StringVar(&options.manHttpsAddr, "manhttps", "", "management https bind addr")
	flag.StringVar(&options.logLevel, "level", "debug", "log level")
	flag.StringVar(&options.logFile, "log", "stdout", "log file, default stdout")
	flag.StringVar(&options.crashLogFile, "crashlog", "", "crash log")
	flag.StringVar(&options.certFile, "certfile", "", "cert file path")
	flag.StringVar(&options.pidFile, "pid", "", "pid file")
	flag.StringVar(&options.keyFile, "keyfile", "", "key file path")
	flag.StringVar(&options.debugHttpAddr, "debughttp", "", "debug http bind addr")
	flag.StringVar(&options.store, "store", "kafka", "backend store")
	flag.StringVar(&options.configFile, "conf", "/etc/kateway.cf", "config file")
	flag.StringVar(&options.killFile, "kill", "", "kill running kateway by pid file")
	flag.StringVar(&options.influxServer, "influxdb", "http://10.77.144.193:10036", "influxdb server address for the metrics reporter")
	flag.BoolVar(&options.showVersion, "version", false, "show version and exit")
	flag.BoolVar(&options.debug, "debug", false, "enable debug mode")
	flag.BoolVar(&options.golangTrace, "gotrace", false, "go tool trace")
	flag.BoolVar(&options.dryRun, "dryrun", false, "dry run mode")
	flag.BoolVar(&options.cpuAffinity, "cpuaffinity", false, "enable cpu affinity")
	flag.BoolVar(&options.ratelimit, "raltelimit", false, "enable rate limit")
	flag.BoolVar(&options.clusterAware, "clusteraware", false, "each kateway knows the cluster nodes")
	flag.BoolVar(&options.disableMetrics, "metricsoff", false, "disable metrics reporter")
	flag.Int64Var(&options.maxPubSize, "maxpub", 1<<20, "max Pub message size")
	flag.IntVar(&options.minPubSize, "minpub", 1, "min Pub message size")
	flag.IntVar(&options.maxPubRetries, "pubretry", 5, "max retries when Pub fails")
	flag.IntVar(&options.pubPoolCapcity, "pubpool", 100, "pub connection pool capacity")
	flag.IntVar(&options.maxClients, "maxclient", 100000, "max concurrent connections")
	flag.DurationVar(&options.offsetCommitInterval, "offsetcommit", time.Minute, "consumer offset commit interval")
	flag.DurationVar(&options.httpReadTimeout, "httprtimeout", time.Second*60, "http server read timeout")
	flag.DurationVar(&options.httpWriteTimeout, "httpwtimeout", time.Second*60, "http server write timeout")
	flag.DurationVar(&options.subTimeout, "subtimeout", time.Minute, "sub timeout before send http 204")
	flag.DurationVar(&options.reporterInterval, "report", time.Second*10, "reporter flush interval")
	flag.DurationVar(&options.metaRefresh, "metarefresh", time.Hour, "meta data refresh interval")
	flag.DurationVar(&options.consoleMetricsInterval, "consolemetrics", 0, "console metrics report interval")
	flag.DurationVar(&options.pubPoolIdleTimeout, "pubpoolidle", 0, "pub pool connect idle timeout")

	flag.Parse()
}

func validateFlags() {
	if options.killFile != "" {
		return
	}

	if options.zone == "" {
		fmt.Fprintf(os.Stderr, "-zone required\n")
		os.Exit(1)
	}

	if options.manHttpsAddr == "" && options.manHttpAddr == "" {
		fmt.Fprintf(os.Stderr, "-manhttp or -manhttps required\n")
	}
}
<|endoftext|>


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

package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"
	"google.golang.org/cloud"
	"google.golang.org/cloud/storage"
)

const (
	uploadURL     = "https://golang.org/dl/upload"
	projectID     = "999119582588"
	storageBucket = "golang"
)

// File represents a file on the golang.org downloads page.
// It should be kept in sync with the (currently Google-interal) download code.
type File struct {
	Filename string
	OS       string
	Arch     string
	Version  string
	Checksum string
	Size     int64
	Kind     string // "archive", "installer", "source"
}

// fileRe matches the files created by the release tool, such as:
//   go1.5beta2.src.tar.gz
//   go1.5.1.linux-386.tar.gz
//   go1.5.windows-amd64.msi
var fileRe = regexp.MustCompile(`^(go[a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+)(?:-([a-z0-9.]+))?)\.(tar\.gz|zip|pkg|msi)$`)

func upload(files []string) error {
	ctx, err := serviceContext()
	if err != nil {
		return err
	}
	for _, name := range files {
		base := filepath.Base(name)
		log.Printf("Uploading %v", base)
		m := fileRe.FindStringSubmatch(base)
		if m == nil {
			return fmt.Errorf("unrecognized file: %q", base)
		}
		var b Build
		version := m[1]
		if m[2] == "src" {
			b.Source = true
		} else {
			b.OS = m[3]
			b.Arch = m[4]
		}
		if err := uploadFile(ctx, &b, version, name); err != nil {
			return err
		}
	}
	return nil
}

func uploadFile(ctx context.Context, b *Build, version, filename string) error {
	file, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	base := filepath.Base(filename)

	// Upload the file to Google Cloud Storage.
	wr := storage.NewWriter(ctx, storageBucket, base)
	wr.ACL = []storage.ACLRule{
		{Entity: storage.AllUsers, Role: storage.RoleReader},
	}
	wr.Write(file)
	if err := wr.Close(); err != nil {
		return fmt.Errorf("uploading file: %v", err)
	}

	// Post file details to golang.org.
	var kind string
	switch {
	case b.Source:
		kind = "source"
	case strings.HasSuffix(base, ".tar.gz"), strings.HasSuffix(base, ".zip"):
		kind = "archive"
	case strings.HasSuffix(base, ".msi"), strings.HasSuffix(base, ".pkg"):
		kind = "installer"
	}
	req, err := json.Marshal(File{
		Filename: base,
		Version:  version,
		OS:       b.OS,
		Arch:     b.Arch,
		Checksum: fmt.Sprintf("%x", sha1.Sum(file)),
		Size:     int64(len(file)),
		Kind:     kind,
	})
	if err != nil {
		return err
	}
	v := url.Values{"user": {*user}, "key": []string{userToken()}}
	u := fmt.Sprintf("%s?%s", uploadURL, v.Encode())
	resp, err := http.Post(u, "application/json", bytes.NewReader(req))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		b, _ := ioutil.ReadAll(resp.Body)
		return fmt.Errorf("upload failed: %v\n%s", resp.Status, b)
	}
	return nil

}

func serviceContext() (context.Context, error) {
	file := filepath.Join(os.Getenv("HOME"), "keys", "golang-org.service.json")
	blob, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, err
	}
	config, err := google.JWTConfigFromJSON(blob, storage.ScopeReadWrite)
	if err != nil {
		return nil, err
	}
	return cloud.NewContext(projectID, config.Client(context.Background())), nil
}
<commit_msg>cmd/release: update to new storage API<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"bytes"
	"crypto/sha1"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/net/context"
	"golang.org/x/oauth2/google"
	"google.golang.org/cloud"
	"google.golang.org/cloud/storage"
)

const (
	uploadURL     = "https://golang.org/dl/upload"
	projectID     = "999119582588"
	storageBucket = "golang"
)

// File represents a file on the golang.org downloads page.
// It should be kept in sync with the (currently Google-interal) download code.
type File struct {
	Filename string
	OS       string
	Arch     string
	Version  string
	Checksum string
	Size     int64
	Kind     string // "archive", "installer", "source"
}

// fileRe matches the files created by the release tool, such as:
//   go1.5beta2.src.tar.gz
//   go1.5.1.linux-386.tar.gz
//   go1.5.windows-amd64.msi
var fileRe = regexp.MustCompile(`^(go[a-z0-9-.]+)\.(src|([a-z0-9]+)-([a-z0-9]+)(?:-([a-z0-9.]+))?)\.(tar\.gz|zip|pkg|msi)$`)

func upload(files []string) error {
	ctx := context.Background()
	c, err := storageClient(ctx)
	if err != nil {
		return err
	}
	defer c.Close()

	for _, name := range files {
		base := filepath.Base(name)
		log.Printf("Uploading %v", base)
		m := fileRe.FindStringSubmatch(base)
		if m == nil {
			return fmt.Errorf("unrecognized file: %q", base)
		}
		var b Build
		version := m[1]
		if m[2] == "src" {
			b.Source = true
		} else {
			b.OS = m[3]
			b.Arch = m[4]
		}
		if err := uploadFile(ctx, c, &b, version, name); err != nil {
			return err
		}
	}
	return nil
}

func uploadFile(ctx context.Context, c *storage.Client, b *Build, version, filename string) error {
	file, err := ioutil.ReadFile(filename)
	if err != nil {
		return err
	}
	base := filepath.Base(filename)

	// Upload the file to Google Cloud Storage.
	wr := c.Bucket(storageBucket).Object(base).NewWriter(ctx)
	wr.ACL = []storage.ACLRule{
		{Entity: storage.AllUsers, Role: storage.RoleReader},
	}
	wr.Write(file)
	if err := wr.Close(); err != nil {
		return fmt.Errorf("uploading file: %v", err)
	}

	// Post file details to golang.org.
	var kind string
	switch {
	case b.Source:
		kind = "source"
	case strings.HasSuffix(base, ".tar.gz"), strings.HasSuffix(base, ".zip"):
		kind = "archive"
	case strings.HasSuffix(base, ".msi"), strings.HasSuffix(base, ".pkg"):
		kind = "installer"
	}
	req, err := json.Marshal(File{
		Filename: base,
		Version:  version,
		OS:       b.OS,
		Arch:     b.Arch,
		Checksum: fmt.Sprintf("%x", sha1.Sum(file)),
		Size:     int64(len(file)),
		Kind:     kind,
	})
	if err != nil {
		return err
	}
	v := url.Values{"user": {*user}, "key": []string{userToken()}}
	u := fmt.Sprintf("%s?%s", uploadURL, v.Encode())
	resp, err := http.Post(u, "application/json", bytes.NewReader(req))
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		b, _ := ioutil.ReadAll(resp.Body)
		return fmt.Errorf("upload failed: %v\n%s", resp.Status, b)
	}
	return nil

}

func storageClient(ctx context.Context) (*storage.Client, error) {
	file := filepath.Join(os.Getenv("HOME"), "keys", "golang-org.service.json")
	blob, err := ioutil.ReadFile(file)
	if err != nil {
		return nil, err
	}
	config, err := google.JWTConfigFromJSON(blob, storage.ScopeReadWrite)
	if err != nil {
		return nil, err
	}
	return storage.NewClient(ctx, cloud.WithBaseHTTP(config.Client(ctx)))
}
<|endoftext|>