<commit_before>package class

import (
	"io"
)

// ClassFile represents a single class file as specified in:
// http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
type ClassFile struct {
	// Magic number found in all valid Java class files.
	// This will always equal 0xCAFEBABE
	Magic uint32

	// Major.Minor denotes the class file version, that
	// has to be supported by the executing JVM.
	MinorVersion uint16
	MajorVersion uint16

	// The constant pool is a table of structures
	// representing various string constants, class,
	// interface & field names and other constants that
	// are referred to in the class file structure.
	ConstPoolSize uint16
	ConstantPool

	// AccessFlags is a mask of flags used to denote
	// access permissions and properties of this class
	// or interface.
	AccessFlags

	// Index into the constant pool, where you should
	// find a CONSTANT_Class_info struct that describes
	// this class.
	ThisClass ConstPoolIndex

	// Index into the constant pool or zero, where you
	// should find a CONSTANT_Class_info struct that
	// describes this class' super class.
	// If SuperClass is zero, then this class must
	// represent the Object class.
	// For an interface, the corresponding value in the
	// constant pool, must represent the Object class.
	SuperClass ConstPoolIndex

	// Interfaces contains indexes into the constant pool,
	// where every referenced entry describes a
	// CONSTANT_Class_info struct representing a direct
	// super-interface of this class or interface.
	Interfaces []ConstPoolIndex

	// Fields contains indexes into the constant pool,
	// referencing field_info structs, giving a complete
	// description of a field in this class or interface.
	// The Fields table only contains fields declared in
	// this class or interface, not any inherited ones.
	Fields []*Field

	// Methods contains method_info structs describing
	// a method of this class or interface.
	// If neiter METHOD_ACC_NATIVE or METHOD_ACC_ABSTRACT
	// flags are set, the corresponding code for the method
	// will also be supplied.
	Methods []*Method

	// Attributes describes properties of this class or
	// interface through attribute_info structs.
	Attributes
}

type Dumper interface {
	Dump(io.Writer) error
}

type Attribute interface {
	Dumper

	Read(io.Reader, ConstantPool) error

	GetTag() AttributeType

	UnknownAttr() *UnknownAttr
	ConstantValue() *ConstantValue
	Code() *Code
	// StackMapTable() *StackMapTable
	Exceptions() *Exceptions
	InnerClasses() *InnerClasses
	EnclosingMethod() *EnclosingMethod
	Synthetic() *Synthetic
	Signature() *Signature
	SourceFile() *SourceFile
	SourceDebugExtension() *SourceDebugExtension
	LineNumberTable() *LineNumberTable
	LocalVariableTable() *LocalVariableTable
	LocalVariableTypeTable() *LocalVariableTypeTable
	Deprecated() *Deprecated
	// RuntimeVisibleAnnotations() *RuntimeVisibleAnnotations
	// RuntimeInvisibleAnnotations() *RuntimeInvisibleAnnotations
	// RuntimeVisibleParameterAnnotations() *RuntimeVisibleParameterAnnotations
	// RuntimeInvisibleParameterAnnotations() *RuntimeInvisibleParameterAnnotations
	// AnnotationDefault() *AnnotationDefault
	BootstrapMethods() *BootstrapMethods
}

type Constant interface {
	Dumper

	Read(io.Reader) error

	GetTag() ConstantType

	Class() *ClassRef
	Field() *FieldRef
	Method() *MethodRef
	InterfaceMethod() *InterfaceMethodRef
	StringRef() *StringRef
	Integer() *IntegerRef
	Float() *FloatRef
	Long() *LongRef
	Double() *DoubleRef
	NameAndType() *NameAndTypeRef
	UTF8() *UTF8Ref
	MethodHandle() *MethodHandleRef
	MethodType() *MethodTypeRef
	InvokeDynamic() *InvokeDynamicRef
}

type Attributes []Attribute
type ConstantPool []Constant

type ConstPoolIndex uint16
type AccessFlags uint16
<commit_msg>Added comments to attribute & dumper interfaces.<commit_after>package class

import (
	"io"
)

// ClassFile represents a single class file as specified in:
// http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html
type ClassFile struct {
	// Magic number found in all valid Java class files.
	// This will always equal 0xCAFEBABE
	Magic uint32

	// Major.Minor denotes the class file version, that
	// has to be supported by the executing JVM.
	MinorVersion uint16
	MajorVersion uint16

	// The constant pool is a table of structures
	// representing various string constants, class,
	// interface & field names and other constants that
	// are referred to in the class file structure.
	ConstPoolSize uint16
	ConstantPool

	// AccessFlags is a mask of flags used to denote
	// access permissions and properties of this class
	// or interface.
	AccessFlags

	// Index into the constant pool, where you should
	// find a CONSTANT_Class_info struct that describes
	// this class.
	ThisClass ConstPoolIndex

	// Index into the constant pool or zero, where you
	// should find a CONSTANT_Class_info struct that
	// describes this class' super class.
	// If SuperClass is zero, then this class must
	// represent the Object class.
	// For an interface, the corresponding value in the
	// constant pool, must represent the Object class.
	SuperClass ConstPoolIndex

	// Interfaces contains indexes into the constant pool,
	// where every referenced entry describes a
	// CONSTANT_Class_info struct representing a direct
	// super-interface of this class or interface.
	Interfaces []ConstPoolIndex

	// Fields contains indexes into the constant pool,
	// referencing field_info structs, giving a complete
	// description of a field in this class or interface.
	// The Fields table only contains fields declared in
	// this class or interface, not any inherited ones.
	Fields []*Field

	// Methods contains method_info structs describing
	// a method of this class or interface.
	// If neiter METHOD_ACC_NATIVE or METHOD_ACC_ABSTRACT
	// flags are set, the corresponding code for the method
	// will also be supplied.
	Methods []*Method

	// Attributes describes properties of this class or
	// interface through attribute_info structs.
	Attributes
}

// All Attributes and Constants, plus the actual class file
// have to fullfill this interface. As you can guess, it's
// used when writing the class file back to its original
// (binary) format.
type Dumper interface {
	Dump(io.Writer) error
}

// Attributes add extra/meta info to ClassFile, Field,
// Method and Code structs. Any JVM implementation or
// Java compiler, may create its own/new attribute(s).
// Though these should not effect the sematics of the program.
// http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7
type Attribute interface {
	Dumper

	Read(io.Reader, ConstantPool) error

	// Think of an Attribute value as a discriminated union.
	GetTag() AttributeType

	// In order to actually access the fields of an attribute
	// you would need a type assertion in your code. But since
	// the Java spec is quite precise on when you can expect
	// what type of attribute (in a valid class file), we can
	// provide "safe" implementations of methods for casting
	// the values, that do not require type assertions.
	// You shouldn't call any of the following functions if you
	// aren't sure about what type an Attribute actually has,
	// since if you are wrong, the function will panic.
	UnknownAttr() *UnknownAttr
	ConstantValue() *ConstantValue
	Code() *Code
	// StackMapTable() *StackMapTable
	Exceptions() *Exceptions
	InnerClasses() *InnerClasses
	EnclosingMethod() *EnclosingMethod
	Synthetic() *Synthetic
	Signature() *Signature
	SourceFile() *SourceFile
	SourceDebugExtension() *SourceDebugExtension
	LineNumberTable() *LineNumberTable
	LocalVariableTable() *LocalVariableTable
	LocalVariableTypeTable() *LocalVariableTypeTable
	Deprecated() *Deprecated
	// RuntimeVisibleAnnotations() *RuntimeVisibleAnnotations
	// RuntimeInvisibleAnnotations() *RuntimeInvisibleAnnotations
	// RuntimeVisibleParameterAnnotations() *RuntimeVisibleParameterAnnotations
	// RuntimeInvisibleParameterAnnotations() *RuntimeInvisibleParameterAnnotations
	// AnnotationDefault() *AnnotationDefault
	BootstrapMethods() *BootstrapMethods
}

type Constant interface {
	Dumper

	Read(io.Reader) error

	GetTag() ConstantType

	Class() *ClassRef
	Field() *FieldRef
	Method() *MethodRef
	InterfaceMethod() *InterfaceMethodRef
	StringRef() *StringRef
	Integer() *IntegerRef
	Float() *FloatRef
	Long() *LongRef
	Double() *DoubleRef
	NameAndType() *NameAndTypeRef
	UTF8() *UTF8Ref
	MethodHandle() *MethodHandleRef
	MethodType() *MethodTypeRef
	InvokeDynamic() *InvokeDynamicRef
}

type Attributes []Attribute
type ConstantPool []Constant

type ConstPoolIndex uint16
type AccessFlags uint16
<|endoftext|>


<commit_before>package nextep

import (
	"context"
	"github.com/jirwin/quadlek/quadlek"
	"github.com/purdyk/tvdb"
	"net/http"
	"time"
	"fmt"
	"errors"
)

var tvdbKey string;

func getTVDBClient(authToken string) (*tvdb.Client) {
	auth := &tvdb.Auth{APIKey: authToken}
	hClient := &http.Client{
		Timeout: 60 * time.Second,
	}
	return tvdb.NewClient(hClient, auth)

}

func findShowId(client *tvdb.Client, name string) (int32, error) {
	results, err := client.Search.ByName(name)

	if err != nil {
		return -1, err
	}

	if len(results) == 0 {
		return -1, errors.New("Found No Results")
	}

	return results[0].ID, nil

}

func findFirstEpisode(client *tvdb.Client, showId int32) (*tvdb.Episode, error) {
	links := &tvdb.Links{}
	links.Next = 1

	params := &tvdb.EpisodeSearchParams{}

	for {
		page := fmt.Sprintf("%d", links.Next)
		params.Page = &page

		results, err := client.Episodes.ListEpisodes(showId, params)

		if err != nil {
			return nil, err
		}

		for _, ep := range results.Data {
			if ep.IsInFuture() {
				return &ep, nil
			}
		}

		links := results.Links

		if !links.HasNext() {
			break
		}
	}

	return nil, errors.New("Failed to find a future episode")

}

func nextEpCommand(ctx context.Context, cmdChannel <-chan *quadlek.CommandMsg) {
	for {
		select {
		case cmdMsg := <-cmdChannel:
			text := cmdMsg.Command.Text
			client := getTVDBClient(tvdbKey)

			id, err := findShowId(client, text)

			if err != nil {
				cmdMsg.Command.Reply() <- &quadlek.CommandResp{
					Text: fmt.Sprintf("Show Search Failed: %s", err),
				}
				continue
			}

			series, err := client.Series.Get(id)

			if err != nil {
				cmdMsg.Command.Reply() <- &quadlek.CommandResp{
					Text: fmt.Sprintf("Series Lookup Failed: %s", err),
				}
				continue
			}

			if series.Status != "Continuing" {
				cmdMsg.Command.Reply() <- &quadlek.CommandResp{
					Text: fmt.Sprintf("Series has ended"),
				}
				continue
			}

			ep, err := findFirstEpisode(client, id)

			if err != nil {
				cmdMsg.Command.Reply() <- &quadlek.CommandResp{
					Text: fmt.Sprintf("Failed to locate first episode: %s", err),
				}
				continue
			}

			cmdMsg.Command.Reply() <- &quadlek.CommandResp{
				Text:      fmt.Sprintf("Next Episode\n\t%s\n\t%s at %s\n", ep.EpisodeName, ep.FirstAired, series.AirsTime),
				InChannel: true,
			}

		case <-ctx.Done():
			return
		}
	}
}

func Register(apikey string) quadlek.Plugin {
	tvdbKey = apikey

	return quadlek.MakePlugin(
		"TVDB",
		[]quadlek.Command{
			quadlek.MakeCommand("nextep", nextEpCommand),
		},
		nil,
		nil,
		nil,
	)
}
<commit_msg>Actually perform the login to obtain token.<commit_after>package nextep

import (
	"context"
	"github.com/jirwin/quadlek/quadlek"
	"github.com/purdyk/tvdb"
	"net/http"
	"time"
	"fmt"
	"errors"
)

var tvdbKey string;

func getTVDBClient(authToken string) (*tvdb.Client) {
	auth := &tvdb.Auth{APIKey: authToken}

	hClient := &http.Client{
		Timeout: 60 * time.Second,
	}

	tClient := tvdb.NewClient(hClient, auth)

	tClient.Token.Login()

	return tClient

}

func findShowId(client *tvdb.Client, name string) (int32, error) {
	results, err := client.Search.ByName(name)

	if err != nil {
		return -1, err
	}

	if len(results) == 0 {
		return -1, errors.New("Found No Results")
	}

	return results[0].ID, nil

}

func findFirstEpisode(client *tvdb.Client, showId int32) (*tvdb.Episode, error) {
	links := &tvdb.Links{}
	links.Next = 1

	params := &tvdb.EpisodeSearchParams{}

	for {
		page := fmt.Sprintf("%d", links.Next)
		params.Page = &page

		results, err := client.Episodes.ListEpisodes(showId, params)

		if err != nil {
			return nil, err
		}

		for _, ep := range results.Data {
			if ep.IsInFuture() {
				return &ep, nil
			}
		}

		links := results.Links

		if !links.HasNext() {
			break
		}
	}

	return nil, errors.New("Failed to find a future episode")

}

func nextEpCommand(ctx context.Context, cmdChannel <-chan *quadlek.CommandMsg) {
	for {
		select {
		case cmdMsg := <-cmdChannel:
			text := cmdMsg.Command.Text
			client := getTVDBClient(tvdbKey)

			id, err := findShowId(client, text)

			if err != nil {
				cmdMsg.Command.Reply() <- &quadlek.CommandResp{
					Text: fmt.Sprintf("Show Search Failed: %s", err),
				}
				continue
			}

			series, err := client.Series.Get(id)

			if err != nil {
				cmdMsg.Command.Reply() <- &quadlek.CommandResp{
					Text: fmt.Sprintf("Series Lookup Failed: %s", err),
				}
				continue
			}

			if series.Status != "Continuing" {
				cmdMsg.Command.Reply() <- &quadlek.CommandResp{
					Text: fmt.Sprintf("Series has ended"),
				}
				continue
			}

			ep, err := findFirstEpisode(client, id)

			if err != nil {
				cmdMsg.Command.Reply() <- &quadlek.CommandResp{
					Text: fmt.Sprintf("Failed to locate first episode: %s", err),
				}
				continue
			}

			cmdMsg.Command.Reply() <- &quadlek.CommandResp{
				Text:      fmt.Sprintf("Next Episode\n\t%s\n\t%s at %s\n", ep.EpisodeName, ep.FirstAired, series.AirsTime),
				InChannel: true,
			}

		case <-ctx.Done():
			return
		}
	}
}

func Register(apikey string) quadlek.Plugin {
	tvdbKey = apikey

	return quadlek.MakePlugin(
		"TVDB",
		[]quadlek.Command{
			quadlek.MakeCommand("nextep", nextEpCommand),
		},
		nil,
		nil,
		nil,
	)
}
<|endoftext|>


<commit_before>// Copyright 2019 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package release 发布版本号管理
package release

import (
	"flag"
	"fmt"
	"io"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/issue9/cmdopt"
	"github.com/issue9/version"

	"github.com/issue9/web/internal/versioninfo"
)

var flagset *flag.FlagSet

// Init 初始化函数
func Init(opt *cmdopt.CmdOpt) {
	flagset = opt.New("release", do, usage)
}

func do(output io.Writer) error {
	ver := flagset.Arg(1)

	// 没有多余的参数，则会显示当前已有的版本号列表
	if ver == "" {
		return outputTags(output)
	}

	if !version.SemVerValid(ver) {
		_, err := fmt.Fprintf(output, "无效的版本号格式：%s", ver)
		return err
	}

	v, err := versioninfo.New("./")
	if err != nil {
		return err
	}

	if err := v.DumpFile(ver); err != nil {
		return err
	}

	// 没有提交消息，则不提交内容到 VCS
	if len(flagset.Args()) <= 2 {
		return nil
	}

	var message string
	message = strings.Join(flagset.Args()[:2], " ")

	// 添加到 git 缓存中
	cmd := exec.Command("git", "add", filepath.Join(v.Path(versioninfo.Path)))
	cmd.Stderr = output
	cmd.Stdout = output
	if err := cmd.Run(); err != nil {
		return err
	}

	cmd = exec.Command("git", "commit", "-m", message)
	cmd.Stderr = output
	cmd.Stdout = output
	if err := cmd.Run(); err != nil {
		return err
	}

	// TODO 检测是否已经存在相同的 git tag

	// 输出 git 标签
	cmd = exec.Command("git", "tag", "v"+ver)
	cmd.Stderr = output
	cmd.Stdout = output

	return cmd.Run()
}

func outputTags(output io.Writer) error {
	cmd := exec.Command("git", "tag")
	cmd.Stdout = output

	return cmd.Run()
}

func usage(output io.Writer) error {
	_, err := fmt.Fprintf(output, `为当前程序发布一个新版本

该操作会在项目的根目录下添加 %s 文件，
并在其中写入版本信息。同时会通过 git tag 命令添加一条 tag 信息。
之后通过 web build 编译，会更新 %s 中的
buildDate 信息，但不会写入文件。

版本号的固定格式为 major.minjor.patch，比如 1.0.1，
git tag 标签中会自动加上 v 前缀，变成 v1.0.1。

一般用法：
git release 0.1.1 [commit message]
`, versioninfo.Path, versioninfo.Path)

	return err
}
<commit_msg>[cmd/web] 更加通用的 web release 参数处理方式<commit_after>// Copyright 2019 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// Package release 发布版本号管理
package release

import (
	"flag"
	"fmt"
	"io"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/issue9/cmdopt"
	"github.com/issue9/version"

	"github.com/issue9/web/internal/versioninfo"
)

var flagset *flag.FlagSet

// Init 初始化函数
func Init(opt *cmdopt.CmdOpt) {
	flagset = opt.New("release", do, usage)
}

func do(output io.Writer) error {
	ver := flagset.Arg(1)

	// 没有多余的参数，则会显示当前已有的版本号列表
	if ver == "" {
		return outputTags(output)
	}

	if ver[0] == 'v' || ver[0] == 'V' {
		ver = ver[1:]
	}

	if !version.SemVerValid(ver) {
		_, err := fmt.Fprintf(output, "无效的版本号格式：%s", ver)
		return err
	}

	v, err := versioninfo.New("./")
	if err != nil {
		return err
	}

	if err := v.DumpFile(ver); err != nil {
		return err
	}

	// 没有提交消息，则不提交内容到 VCS
	if len(flagset.Args()) <= 2 {
		return nil
	}

	var message string
	message = strings.Join(flagset.Args()[:2], " ")

	// 添加到 git 缓存中
	cmd := exec.Command("git", "add", filepath.Join(v.Path(versioninfo.Path)))
	cmd.Stderr = output
	cmd.Stdout = output
	if err := cmd.Run(); err != nil {
		return err
	}

	cmd = exec.Command("git", "commit", "-m", message)
	cmd.Stderr = output
	cmd.Stdout = output
	if err := cmd.Run(); err != nil {
		return err
	}

	// 输出 git 标签
	cmd = exec.Command("git", "tag", "v"+ver)
	cmd.Stderr = output
	cmd.Stdout = output

	return cmd.Run()
}

func outputTags(output io.Writer) error {
	cmd := exec.Command("git", "tag")
	cmd.Stdout = output

	return cmd.Run()
}

func usage(output io.Writer) error {
	_, err := fmt.Fprintf(output, `为当前程序发布一个新版本

该操作会在项目的根目录下添加 %s 文件，
并在其中写入版本信息。之后通过 web build 编译，
会更新 %s 中的 buildDate 信息，但不会写入文件。
同时根据参数决定是否用 git tag 命令添加一条 tag 信息。

版本号的固定格式为 major.minjor.patch，比如 1.0.1，当然 v1.0.1 也会被正确处理。
git tag 标签中会自动加上 v 前缀，变成 v1.0.1。

一般用法：
git release 0.1.1 [commit message]
`, versioninfo.Path, versioninfo.Path)

	return err
}
<|endoftext|>


<commit_before>package command

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

func countSettingsKeys(t *testing.T, m map[string]interface{}) int {
	t.Helper()

	var keys int
	for _, v := range m {
		if sub, ok := v.(map[string]interface{}); ok {
			// Don't count the object, just its keys.
			keys += len(sub)
		}

		if _, ok := v.(string); ok {
			// v is just a string key.
			keys++
		}

		if _, ok := v.([]string); ok {
			// v is just a string key.
			keys++
		}
	}
	return keys
}

func TestSetNotiDefaults(t *testing.T) {
	v := viper.New()
	setNotiDefaults(v)

	haveKeys := countSettingsKeys(t, v.AllSettings())
	if haveKeys != len(baseDefaults) {
		t.Error("Unexpected base config length")
		t.Errorf("have=%d; want=%d", haveKeys, len(baseDefaults))
	}
}

func getNotiEnv(t *testing.T) map[string]string {
	t.Helper()

	notiEnv := make(map[string]string)
	for _, env := range keyEnvBindings {
		notiEnv[env] = os.Getenv(env)
	}
	return notiEnv
}

func clearNotiEnv(t *testing.T) {
	t.Helper()

	for _, env := range keyEnvBindings {
		if err := os.Unsetenv(env); err != nil {
			t.Fatalf("failed to clear noti env: %s", err)
		}
	}
}

func setNotiEnv(t *testing.T, m map[string]string) {
	t.Helper()

	for env, val := range m {
		if err := os.Setenv(env, val); err != nil {
			t.Fatalf("failed to set noti env: %s", err)
		}
	}
}

func TestBindNotiEnv(t *testing.T) {
	orig := getNotiEnv(t)
	defer setNotiEnv(t, orig)

	clearNotiEnv(t)

	v := viper.New()
	bindNotiEnv(v)

	haveKeys := countSettingsKeys(t, v.AllSettings())
	if haveKeys != 0 {
		t.Fatal("Environment should be cleared")
	}

	for _, env := range keyEnvBindings {
		if err := os.Setenv(env, "foo"); err != nil {
			t.Errorf("Setenv error: %s", err)
		}
	}

	haveKeys = countSettingsKeys(t, v.AllSettings())
	wantKeys := len(baseDefaults) - 2 // -1 for message, -1 for default.
	if haveKeys != wantKeys {
		t.Error("Unexpected base config length")
		t.Errorf("have=%d; want=%d", haveKeys, wantKeys)
	}
}

func TestSetupConfigFile(t *testing.T) {
	v := viper.New()
	// For tests, we prepend the testdata dir so that we check for a config
	// file there first.
	v.AddConfigPath("testdata")
	setupConfigFile(v)

	const want = 1
	have := countSettingsKeys(t, v.AllSettings())
	if have != want {
		t.Error("Unexpected number of keys")
		t.Errorf("have=%d; want=%d", have, want)
	}
}

func TestConfigureApp(t *testing.T) {
	orig := getNotiEnv(t)
	defer setNotiEnv(t, orig)
	clearNotiEnv(t)

	v := viper.New()
	// For tests, we prepend the testdata dir so that we check for a config
	// file there first.
	v.AddConfigPath("testdata")
	flags := pflag.NewFlagSet("testconfigureapp", pflag.ContinueOnError)
	defineFlags(flags)

	configureApp(v, flags)

	configDir := filepath.Base(filepath.Dir(v.ConfigFileUsed()))
	if configDir != "testdata" {
		t.Fatalf("Wrong config file used: %s", v.ConfigFileUsed())
	}

	t.Run("default and file", func(t *testing.T) {
		// File takes precedence.
		have := v.GetString("nsuser.soundName")
		want := "testdata"
		if have != want {
			t.Error("Unexpected config value")
			t.Errorf("have=%s; want=%s", have, want)
		}
	})

	t.Run("default, file, and env", func(t *testing.T) {
		// Env takes precedence.
		want := "foo"
		if err := os.Setenv("NOTI_SOUND", want); err != nil {
			t.Errorf("Failed to set env: %s", err)
		}
		defer setNotiEnv(t, orig)

		have := v.GetString("nsuser.soundName")
		if have != want {
			t.Error("Unexpected config value")
			t.Errorf("have=%s; want=%s", have, want)
		}
	})

	t.Run("default", func(t *testing.T) {
		// Default takes precedence.

		// Clear config file.
		v.ReadConfig(strings.NewReader(""))

		have := v.GetString("nsuser.soundName")
		want := baseDefaults["nsuser.soundName"]
		if have != want {
			t.Error("Unexpected config value")
			t.Errorf("have=%s; want=%s", have, want)
		}
	})
}

func TestEnabledServices(t *testing.T) {
	orig := getNotiEnv(t)
	defer setNotiEnv(t, orig)
	clearNotiEnv(t)

	t.Run("flag override", func(t *testing.T) {
		v := viper.New()
		flags := pflag.NewFlagSet("testenabledservices", pflag.ContinueOnError)
		defineFlags(flags)

		want := true
		flags.Set("slack", fmt.Sprint(want))
		services := enabledServices(v, flags)

		if len(services) != 1 {
			t.Error("Unexpected number of enabled services")
			t.Errorf("have=%d; want=%d", len(services), 1)
		}

		_, have := services["slack"]
		if have != want {
			t.Error("Unexpected enabled state")
			t.Errorf("have=%t; want=%t", have, want)
		}
	})

	t.Run("env override", func(t *testing.T) {
		v := viper.New()
		flags := pflag.NewFlagSet("testenabledservices", pflag.ContinueOnError)
		defineFlags(flags)

		if err := os.Setenv("NOTI_DEFAULT", "slack"); err != nil {
			t.Fatal(err)
		}
		defer os.Unsetenv("NOTI_DEFAULT")

		services := enabledServices(v, flags)

		if len(services) != 1 {
			t.Error("Unexpected number of enabled services")
			t.Errorf("have=%d; want=%d", len(services), 1)
		}

		_, have := services["slack"]
		want := true
		if have != want {
			t.Error("Unexpected enabled state")
			t.Errorf("have=%t; want=%t", have, want)
		}
	})

	t.Run("defaults", func(t *testing.T) {
		v := viper.New()
		// For tests, we prepend the testdata dir so that we check for a config
		// file there first.
		v.AddConfigPath("testdata")

		flags := pflag.NewFlagSet("testenabledservices", pflag.ContinueOnError)
		defineFlags(flags)

		configureApp(v, flags)

		services := enabledServices(v, flags)

		if len(services) != 1 {
			t.Error("Unexpected number of enabled services")
			t.Errorf("have=%d; want=%d", len(services), 1)
		}

		_, have := services["banner"]
		want := true
		if have != want {
			t.Error("Unexpected enabled state")
			t.Errorf("have=%t; want=%t", have, want)
		}
	})
}
<commit_msg>Test getNotifications<commit_after>package command

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

func countSettingsKeys(t *testing.T, m map[string]interface{}) int {
	t.Helper()

	var keys int
	for _, v := range m {
		if sub, ok := v.(map[string]interface{}); ok {
			// Don't count the object, just its keys.
			keys += len(sub)
		}

		if _, ok := v.(string); ok {
			// v is just a string key.
			keys++
		}

		if _, ok := v.([]string); ok {
			// v is just a string key.
			keys++
		}
	}
	return keys
}

func TestSetNotiDefaults(t *testing.T) {
	v := viper.New()
	setNotiDefaults(v)

	haveKeys := countSettingsKeys(t, v.AllSettings())
	if haveKeys != len(baseDefaults) {
		t.Error("Unexpected base config length")
		t.Errorf("have=%d; want=%d", haveKeys, len(baseDefaults))
	}
}

func getNotiEnv(t *testing.T) map[string]string {
	t.Helper()

	notiEnv := make(map[string]string)
	for _, env := range keyEnvBindings {
		notiEnv[env] = os.Getenv(env)
	}
	return notiEnv
}

func clearNotiEnv(t *testing.T) {
	t.Helper()

	for _, env := range keyEnvBindings {
		if err := os.Unsetenv(env); err != nil {
			t.Fatalf("failed to clear noti env: %s", err)
		}
	}
}

func setNotiEnv(t *testing.T, m map[string]string) {
	t.Helper()

	for env, val := range m {
		if err := os.Setenv(env, val); err != nil {
			t.Fatalf("failed to set noti env: %s", err)
		}
	}
}

func TestBindNotiEnv(t *testing.T) {
	orig := getNotiEnv(t)
	defer setNotiEnv(t, orig)

	clearNotiEnv(t)

	v := viper.New()
	bindNotiEnv(v)

	haveKeys := countSettingsKeys(t, v.AllSettings())
	if haveKeys != 0 {
		t.Fatal("Environment should be cleared")
	}

	for _, env := range keyEnvBindings {
		if err := os.Setenv(env, "foo"); err != nil {
			t.Errorf("Setenv error: %s", err)
		}
	}

	haveKeys = countSettingsKeys(t, v.AllSettings())
	wantKeys := len(baseDefaults) - 2 // -1 for message, -1 for default.
	if haveKeys != wantKeys {
		t.Error("Unexpected base config length")
		t.Errorf("have=%d; want=%d", haveKeys, wantKeys)
	}
}

func TestSetupConfigFile(t *testing.T) {
	v := viper.New()
	// For tests, we prepend the testdata dir so that we check for a config
	// file there first.
	v.AddConfigPath("testdata")
	setupConfigFile(v)

	const want = 1
	have := countSettingsKeys(t, v.AllSettings())
	if have != want {
		t.Error("Unexpected number of keys")
		t.Errorf("have=%d; want=%d", have, want)
	}
}

func TestConfigureApp(t *testing.T) {
	orig := getNotiEnv(t)
	defer setNotiEnv(t, orig)
	clearNotiEnv(t)

	v := viper.New()
	// For tests, we prepend the testdata dir so that we check for a config
	// file there first.
	v.AddConfigPath("testdata")
	flags := pflag.NewFlagSet("testconfigureapp", pflag.ContinueOnError)
	defineFlags(flags)

	configureApp(v, flags)

	configDir := filepath.Base(filepath.Dir(v.ConfigFileUsed()))
	if configDir != "testdata" {
		t.Fatalf("Wrong config file used: %s", v.ConfigFileUsed())
	}

	t.Run("default and file", func(t *testing.T) {
		// File takes precedence.
		have := v.GetString("nsuser.soundName")
		want := "testdata"
		if have != want {
			t.Error("Unexpected config value")
			t.Errorf("have=%s; want=%s", have, want)
		}
	})

	t.Run("default, file, and env", func(t *testing.T) {
		// Env takes precedence.
		want := "foo"
		if err := os.Setenv("NOTI_SOUND", want); err != nil {
			t.Errorf("Failed to set env: %s", err)
		}
		defer setNotiEnv(t, orig)

		have := v.GetString("nsuser.soundName")
		if have != want {
			t.Error("Unexpected config value")
			t.Errorf("have=%s; want=%s", have, want)
		}
	})

	t.Run("default", func(t *testing.T) {
		// Default takes precedence.

		// Clear config file.
		v.ReadConfig(strings.NewReader(""))

		have := v.GetString("nsuser.soundName")
		want := baseDefaults["nsuser.soundName"]
		if have != want {
			t.Error("Unexpected config value")
			t.Errorf("have=%s; want=%s", have, want)
		}
	})
}

func TestEnabledServices(t *testing.T) {
	orig := getNotiEnv(t)
	defer setNotiEnv(t, orig)
	clearNotiEnv(t)

	t.Run("flag override", func(t *testing.T) {
		v := viper.New()
		flags := pflag.NewFlagSet("testenabledservices", pflag.ContinueOnError)
		defineFlags(flags)

		want := true
		flags.Set("slack", fmt.Sprint(want))
		services := enabledServices(v, flags)

		if len(services) != 1 {
			t.Error("Unexpected number of enabled services")
			t.Errorf("have=%d; want=%d", len(services), 1)
		}

		_, have := services["slack"]
		if have != want {
			t.Error("Unexpected enabled state")
			t.Errorf("have=%t; want=%t", have, want)
		}
	})

	t.Run("env override", func(t *testing.T) {
		v := viper.New()
		flags := pflag.NewFlagSet("testenabledservices", pflag.ContinueOnError)
		defineFlags(flags)

		if err := os.Setenv("NOTI_DEFAULT", "slack"); err != nil {
			t.Fatal(err)
		}
		defer os.Unsetenv("NOTI_DEFAULT")

		services := enabledServices(v, flags)

		if len(services) != 1 {
			t.Error("Unexpected number of enabled services")
			t.Errorf("have=%d; want=%d", len(services), 1)
		}

		_, have := services["slack"]
		want := true
		if have != want {
			t.Error("Unexpected enabled state")
			t.Errorf("have=%t; want=%t", have, want)
		}
	})

	t.Run("defaults", func(t *testing.T) {
		v := viper.New()
		// For tests, we prepend the testdata dir so that we check for a config
		// file there first.
		v.AddConfigPath("testdata")

		flags := pflag.NewFlagSet("testenabledservices", pflag.ContinueOnError)
		defineFlags(flags)

		configureApp(v, flags)

		services := enabledServices(v, flags)

		if len(services) != 1 {
			t.Error("Unexpected number of enabled services")
			t.Errorf("have=%d; want=%d", len(services), 1)
		}

		_, have := services["banner"]
		want := true
		if have != want {
			t.Error("Unexpected enabled state")
			t.Errorf("have=%t; want=%t", have, want)
		}
	})
}

func TestGetNotifications(t *testing.T) {
	services := []string{
		"banner",
		"bearychat",
		"hipchat",
		"pushbullet",
		"pushover",
		"pushsafer",
		"simplepush",
		"slack",
		"speech",
	}

	for _, name := range services {
		t.Run(fmt.Sprintf("get %s notification", name), func(t *testing.T) {
			v := viper.New()
			s := map[string]struct{}{name: struct{}{}}

			notis := getNotifications(v, s)
			if len(notis) != 1 {
				t.Error("Unexpected number of notifications")
				t.Errorf("have=%d; want=%d", len(notis), 1)
			}
		})
	}
}
<|endoftext|>


<commit_before>/*
   Copyright 2014 CoreOS, Inc.

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

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

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

package client

import (
	"net/http"
	"net/url"
	"path"

	"github.com/coreos/fleet/Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi"

	"github.com/coreos/fleet/machine"
	"github.com/coreos/fleet/schema"
)

func NewHTTPClient(c *http.Client, ep url.URL) (API, error) {
	svc, err := schema.New(c)
	if err != nil {
		return nil, err
	}

	// append a slash so the schema.Service knows this is the root path
	ep.Path = path.Join(ep.Path, "v1-alpha") + "/"
	svc.BasePath = ep.String()

	return &HTTPClient{svc: svc}, nil
}

type HTTPClient struct {
	svc *schema.Service

	//NOTE(bcwaldon): This is only necessary until the API interface
	// is fully implemented by HTTPClient
	API
}

func (c *HTTPClient) Machines() ([]machine.MachineState, error) {
	machines := make([]machine.MachineState, 0)
	call := c.svc.Machines.List()
	for call != nil {
		page, err := call.Do()
		if err != nil {
			return nil, err
		}

		machines = append(machines, schema.MapSchemaToMachineStates(page.Machines)...)

		if len(page.NextPageToken) > 0 {
			call = c.svc.Machines.List()
			call.NextPageToken(page.NextPageToken)
		} else {
			call = nil
		}
	}
	return machines, nil
}

func (c *HTTPClient) Units() ([]*schema.Unit, error) {
	var units []*schema.Unit
	call := c.svc.Units.List()
	for call != nil {
		page, err := call.Do()
		if err != nil {
			return nil, err
		}

		units = append(units, page.Units...)

		if len(page.NextPageToken) > 0 {
			call = c.svc.Units.List()
			call.NextPageToken(page.NextPageToken)
		} else {
			call = nil
		}
	}
	return units, nil
}

func (c *HTTPClient) Unit(name string) (*schema.Unit, error) {
	u, err := c.svc.Units.Get(name).Do()
	if err != nil && !is404(err) {
		return nil, err
	}
	return u, nil
}

func (c *HTTPClient) UnitStates() ([]*schema.UnitState, error) {
	var states []*schema.UnitState
	call := c.svc.UnitState.List()
	for call != nil {
		page, err := call.Do()
		if err != nil {
			return nil, err
		}

		states = append(states, page.States...)

		if len(page.NextPageToken) > 0 {
			call = c.svc.UnitState.List()
			call.NextPageToken(page.NextPageToken)
		} else {
			call = nil
		}
	}
	return states, nil
}

func (c *HTTPClient) DestroyUnit(name string) error {
	return c.svc.Units.Delete(name).Do()
}

func (c *HTTPClient) CreateUnit(u *schema.Unit) error {
	return c.svc.Units.Set(u.Name, u).Do()
}

func (c *HTTPClient) SetUnitTargetState(name, target string) error {
	u := schema.Unit{
		Name:         name,
		DesiredState: target,
	}
	return c.svc.Units.Set(name, &u).Do()
}

func is404(err error) bool {
	googerr, ok := err.(*googleapi.Error)
	return ok && googerr.Code == http.StatusNotFound
}
<commit_msg>client: use new non-alpha fleet API path<commit_after>/*
   Copyright 2014 CoreOS, Inc.

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

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

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

package client

import (
	"net/http"
	"net/url"
	"path"

	"github.com/coreos/fleet/Godeps/_workspace/src/code.google.com/p/google-api-go-client/googleapi"

	"github.com/coreos/fleet/machine"
	"github.com/coreos/fleet/schema"
)

func NewHTTPClient(c *http.Client, ep url.URL) (API, error) {
	svc, err := schema.New(c)
	if err != nil {
		return nil, err
	}

	// append a slash so the schema.Service knows this is the root path
	ep.Path = path.Join(ep.Path, "fleet", "v1") + "/"
	svc.BasePath = ep.String()

	return &HTTPClient{svc: svc}, nil
}

type HTTPClient struct {
	svc *schema.Service

	//NOTE(bcwaldon): This is only necessary until the API interface
	// is fully implemented by HTTPClient
	API
}

func (c *HTTPClient) Machines() ([]machine.MachineState, error) {
	machines := make([]machine.MachineState, 0)
	call := c.svc.Machines.List()
	for call != nil {
		page, err := call.Do()
		if err != nil {
			return nil, err
		}

		machines = append(machines, schema.MapSchemaToMachineStates(page.Machines)...)

		if len(page.NextPageToken) > 0 {
			call = c.svc.Machines.List()
			call.NextPageToken(page.NextPageToken)
		} else {
			call = nil
		}
	}
	return machines, nil
}

func (c *HTTPClient) Units() ([]*schema.Unit, error) {
	var units []*schema.Unit
	call := c.svc.Units.List()
	for call != nil {
		page, err := call.Do()
		if err != nil {
			return nil, err
		}

		units = append(units, page.Units...)

		if len(page.NextPageToken) > 0 {
			call = c.svc.Units.List()
			call.NextPageToken(page.NextPageToken)
		} else {
			call = nil
		}
	}
	return units, nil
}

func (c *HTTPClient) Unit(name string) (*schema.Unit, error) {
	u, err := c.svc.Units.Get(name).Do()
	if err != nil && !is404(err) {
		return nil, err
	}
	return u, nil
}

func (c *HTTPClient) UnitStates() ([]*schema.UnitState, error) {
	var states []*schema.UnitState
	call := c.svc.UnitState.List()
	for call != nil {
		page, err := call.Do()
		if err != nil {
			return nil, err
		}

		states = append(states, page.States...)

		if len(page.NextPageToken) > 0 {
			call = c.svc.UnitState.List()
			call.NextPageToken(page.NextPageToken)
		} else {
			call = nil
		}
	}
	return states, nil
}

func (c *HTTPClient) DestroyUnit(name string) error {
	return c.svc.Units.Delete(name).Do()
}

func (c *HTTPClient) CreateUnit(u *schema.Unit) error {
	return c.svc.Units.Set(u.Name, u).Do()
}

func (c *HTTPClient) SetUnitTargetState(name, target string) error {
	u := schema.Unit{
		Name:         name,
		DesiredState: target,
	}
	return c.svc.Units.Set(name, &u).Do()
}

func is404(err error) bool {
	googerr, ok := err.(*googleapi.Error)
	return ok && googerr.Code == http.StatusNotFound
}
<|endoftext|>


<commit_before>package goinsta

import (
	"encoding/json"
	"errors"
	"fmt"
)

type Users struct {
	inst *Instagram

	// It's a bit confusing have the same structure
	// in the Instagram strucure and in the multiple users
	// calls

	err      error
	endpoint string

	Status   string `json:"status"`
	BigList  bool   `json:"big_list"`
	Users    []User `json:"users"`
	PageSize int    `json:"page_size"`
	NextID   string `json:"next_max_id"`
}

func newUsers(inst *Instagram) *Users {
	users := &Users{inst: inst}

	return users
}

// SetInstagram sets new instagram to user structure
func (users *Users) SetInstagram(inst *Instagram) {
	users.inst = inst
}

var ErrNoMore = errors.New("User list end reached")

// Next allows to paginate after calling:
// Account.Follow* and User.Follow*
//
// New user list is stored inside Users
//
// returns false when list reach the end.
func (users *Users) Next() bool {
	if users.err != nil {
		return false
	}

	insta := users.inst
	endpoint := users.endpoint

	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: endpoint,
			Query: map[string]string{
				"max_id":             users.NextID,
				"ig_sig_key_version": goInstaSigKeyVersion,
				"rank_token":         insta.rankToken,
			},
		},
	)
	if err == nil {
		usrs := Users{}
		err = json.Unmarshal(body, &usrs)
		if err == nil {
			*users = usrs
			if !usrs.BigList || usrs.NextID == "" {
				users.err = ErrNoMore
			}
			users.inst = insta
			users.endpoint = endpoint
			return true
		}
	}
	users.err = err
	return false
}

type userResp struct {
	Status string `json:"status"`
	User   User   `json:"user"`
}

// User is the representation of instagram's user profile
type User struct {
	inst *Instagram

	ID                         int64        `json:"pk"`
	Username                   string       `json:"username"`
	FullName                   string       `json:"full_name"`
	Biography                  string       `json:"biography"`
	ProfilePicURL              string       `json:"profile_pic_url"`
	Email                      string       `json:"email"`
	PhoneNumber                string       `json:"phone_number"`
	IsBusiness                 bool         `json:"is_business"`
	Gender                     int          `json:"gender"`
	ProfilePicID               string       `json:"profile_pic_id"`
	HasAnonymousProfilePicture bool         `json:"has_anonymous_profile_picture"`
	IsPrivate                  bool         `json:"is_private"`
	IsUnpublished              bool         `json:"is_unpublished"`
	AllowedCommenterType       string       `json:"allowed_commenter_type"`
	IsVerified                 bool         `json:"is_verified"`
	MediaCount                 int          `json:"media_count"`
	FollowerCount              int          `json:"follower_count"`
	FollowingCount             int          `json:"following_count"`
	GeoMediaCount              int          `json:"geo_media_count"`
	ExternalURL                string       `json:"external_url"`
	HasBiographyTranslation    bool         `json:"has_biography_translation"`
	ExternalLynxURL            string       `json:"external_lynx_url"`
	HdProfilePicURLInfo        PicURLInfo   `json:"hd_profile_pic_url_info"`
	HdProfilePicVersions       []PicURLInfo `json:"hd_profile_pic_versions"`
	UsertagsCount              int          `json:"usertags_count"`
	HasChaining                bool         `json:"has_chaining"`
	IsFavorite                 bool         `json:"is_favorite"`
	ReelAutoArchive            string       `json:"reel_auto_archive"`
	School                     School       `json:"school"`
	PublicEmail                string       `json:"public_email"`
	PublicPhoneNumber          string       `json:"public_phone_number"`
	PublicPhoneCountryCode     string       `json:"public_phone_country_code"`
	ContactPhoneNumber         string       `json:"contact_phone_number"`
	CityID                     int64        `json:"city_id"`
	CityName                   string       `json:"city_name"`
	AddressStreet              string       `json:"address_street"`
	DirectMessaging            string       `json:"direct_messaging"`
	Latitude                   float64      `json:"latitude"`
	Longitude                  float64      `json:"longitude"`
	Category                   string       `json:"category"`
	BusinessContactMethod      string       `json:"business_contact_method"`
	IsCallToActionEnabled      bool         `json:"is_call_to_action_enabled"`
	FbPageCallToActionID       string       `json:"fb_page_call_to_action_id"`
	Zip                        string       `json:"zip"`
	Friendship                 Friendship   `json:"friendship_status"`
}

// Following returns a list of user following.
//
// Users.Next can be used to paginate
func (user *User) Following() *Users {
	users := &Users{}
	users.inst = user.inst
	users.endpoint = fmt.Sprintf(urlFollowing, user.ID)
	return users
}

// Followers returns a list of user followers.
//
// Users.Next can be used to paginate
func (user *User) Followers() *Users {
	users := &Users{}
	users.inst = user.inst
	users.endpoint = fmt.Sprintf(urlFollowers, user.ID)
	return users
}

// Block blocks user
//
// This function updates current User.Friendship structure.
func (user *User) Block() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err == nil {
		body, err := insta.sendRequest(
			&reqOptions{
				Endpoint: fmt.Sprintf(urlUserBlock, user.ID),
				Query:    generateSignature(data),
				IsPost:   true,
			},
		)
		if err == nil {
			resp := friendResp{}
			err = json.Unmarshal(body, &resp)
			user.Friendship = resp.Friendship
		}
	}
	return err
}

// Unblock unblocks user
//
// This function updates current User.Friendship structure.
func (user *User) Unblock() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err == nil {
		body, err := insta.sendRequest(
			&reqOptions{
				Endpoint: fmt.Sprintf(urlUserUnblock, user.ID),
				Query:    generateSignature(data),
				IsPost:   true,
			},
		)
		if err == nil {
			resp := friendResp{}
			err = json.Unmarshal(body, &resp)
			user.Friendship = resp.Friendship
		}
	}
	return err
}

// Follow started following some user
//
// This function performs a follow call. If user is private
// you have to wait until he/she accepts you.
//
// If the account is public User.Friendship will be updated
func (user *User) Follow() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err == nil {
		body, err := insta.sendRequest(
			&reqOptions{
				Endpoint: fmt.Sprintf(urlUserFollow, user.ID),
				Query:    generateSignature(data),
				IsPost:   true,
			},
		)
		if err == nil {
			resp := friendResp{}
			err = json.Unmarshal(body, &resp)
			user.Friendship = resp.Friendship
		}
	}
	return err
}

// Unfollow unfollows user
//
// User.Friendship will be updated
func (user *User) Unfollow() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err == nil {
		body, err := insta.sendRequest(
			&reqOptions{
				Endpoint: fmt.Sprintf(urlUserUnfollow, user.ID),
				Query:    generateSignature(data),
				IsPost:   true,
			},
		)
		if err == nil {
			resp := friendResp{}
			err = json.Unmarshal(body, &resp)
			user.Friendship = resp.Friendship
		}
	}
	return err
}

func (user *User) friendShip() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err != nil {
		return err
	}

	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: fmt.Sprintf(urlFriendship, user.ID),
			Query:    generateSignature(data),
			IsPost:   true,
		},
	)
	if err == nil {
		err = json.Unmarshal(body, &user.Friendship)
	}
	return err
}

// Feed returns user feeds (media)
//
// minTime is the minimum timestamp of media.
//
// For pagination use FeedMedia.Next()
func (user *User) Feed(minTime []byte) *FeedMedia {
	insta := user.inst
	timestamp := b2s(minTime)

	media := &FeedMedia{}
	media.timestamp = timestamp
	media.inst = insta
	media.endpoint = urlUserFeed
	media.uid = user.ID
	return media
}

// Stories returns user stories
//
// Use StoryMedia.Next for pagination.
//
// See example: examples/user/stories.go
func (user *User) Stories() *StoryMedia {
	media := &StoryMedia{}
	media.uid = user.ID
	media.inst = user.inst
	media.endpoint = urlUserStories
	return media
}

// Tags returns media where user is tagged in
//
// For pagination use FeedMedia.Next()
func (user *User) Tags(minTimestamp []byte) (*FeedMedia, error) {
	timestamp := b2s(minTimestamp)
	body, err := user.inst.sendRequest(
		&reqOptions{
			Endpoint: fmt.Sprintf(urlUserTags, user.ID),
			Query: map[string]string{
				"max_id":         "",
				"rank_token":     user.inst.rankToken,
				"min_timestamp":  timestamp,
				"ranked_content": "true",
			},
		},
	)
	if err != nil {
		return nil, err
	}

	media := &FeedMedia{}
	err = json.Unmarshal(body, media)
	media.inst = user.inst
	media.endpoint = urlUserTags
	media.uid = user.ID
	return media, err
}
<commit_msg>Added comments<commit_after>package goinsta

import (
	"encoding/json"
	"errors"
	"fmt"
)

type Users struct {
	inst *Instagram

	// It's a bit confusing have the same structure
	// in the Instagram strucure and in the multiple users
	// calls

	err      error
	endpoint string

	Status   string `json:"status"`
	BigList  bool   `json:"big_list"`
	Users    []User `json:"users"`
	PageSize int    `json:"page_size"`
	NextID   string `json:"next_max_id"`
}

func newUsers(inst *Instagram) *Users {
	users := &Users{inst: inst}

	return users
}

// SetInstagram sets new instagram to user structure
func (users *Users) SetInstagram(inst *Instagram) {
	users.inst = inst
}

var ErrNoMore = errors.New("User list end reached")

// Next allows to paginate after calling:
// Account.Follow* and User.Follow*
//
// New user list is stored inside Users
//
// returns false when list reach the end.
func (users *Users) Next() bool {
	if users.err != nil {
		return false
	}

	insta := users.inst
	endpoint := users.endpoint

	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: endpoint,
			Query: map[string]string{
				"max_id":             users.NextID,
				"ig_sig_key_version": goInstaSigKeyVersion,
				"rank_token":         insta.rankToken,
			},
		},
	)
	if err == nil {
		usrs := Users{}
		err = json.Unmarshal(body, &usrs)
		if err == nil {
			*users = usrs
			if !usrs.BigList || usrs.NextID == "" {
				users.err = ErrNoMore
			}
			users.inst = insta
			users.endpoint = endpoint
			return true
		}
	}
	users.err = err
	return false
}

type userResp struct {
	Status string `json:"status"`
	User   User   `json:"user"`
}

// User is the representation of instagram's user profile
type User struct {
	inst *Instagram

	ID                         int64        `json:"pk"`
	Username                   string       `json:"username"`
	FullName                   string       `json:"full_name"`
	Biography                  string       `json:"biography"`
	ProfilePicURL              string       `json:"profile_pic_url"`
	Email                      string       `json:"email"`
	PhoneNumber                string       `json:"phone_number"`
	IsBusiness                 bool         `json:"is_business"`
	Gender                     int          `json:"gender"`
	ProfilePicID               string       `json:"profile_pic_id"`
	HasAnonymousProfilePicture bool         `json:"has_anonymous_profile_picture"`
	IsPrivate                  bool         `json:"is_private"`
	IsUnpublished              bool         `json:"is_unpublished"`
	AllowedCommenterType       string       `json:"allowed_commenter_type"`
	IsVerified                 bool         `json:"is_verified"`
	MediaCount                 int          `json:"media_count"`
	FollowerCount              int          `json:"follower_count"`
	FollowingCount             int          `json:"following_count"`
	GeoMediaCount              int          `json:"geo_media_count"`
	ExternalURL                string       `json:"external_url"`
	HasBiographyTranslation    bool         `json:"has_biography_translation"`
	ExternalLynxURL            string       `json:"external_lynx_url"`
	HdProfilePicURLInfo        PicURLInfo   `json:"hd_profile_pic_url_info"`
	HdProfilePicVersions       []PicURLInfo `json:"hd_profile_pic_versions"`
	UsertagsCount              int          `json:"usertags_count"`
	HasChaining                bool         `json:"has_chaining"`
	IsFavorite                 bool         `json:"is_favorite"`
	ReelAutoArchive            string       `json:"reel_auto_archive"`
	School                     School       `json:"school"`
	PublicEmail                string       `json:"public_email"`
	PublicPhoneNumber          string       `json:"public_phone_number"`
	PublicPhoneCountryCode     string       `json:"public_phone_country_code"`
	ContactPhoneNumber         string       `json:"contact_phone_number"`
	CityID                     int64        `json:"city_id"`
	CityName                   string       `json:"city_name"`
	AddressStreet              string       `json:"address_street"`
	DirectMessaging            string       `json:"direct_messaging"`
	Latitude                   float64      `json:"latitude"`
	Longitude                  float64      `json:"longitude"`
	Category                   string       `json:"category"`
	BusinessContactMethod      string       `json:"business_contact_method"`
	IsCallToActionEnabled      bool         `json:"is_call_to_action_enabled"`
	FbPageCallToActionID       string       `json:"fb_page_call_to_action_id"`
	Zip                        string       `json:"zip"`
	Friendship                 Friendship   `json:"friendship_status"`
}

// Following returns a list of user following.
//
// Users.Next can be used to paginate
//
// See example: examples/user/following.go
func (user *User) Following() *Users {
	users := &Users{}
	users.inst = user.inst
	users.endpoint = fmt.Sprintf(urlFollowing, user.ID)
	return users
}

// Followers returns a list of user followers.
//
// Users.Next can be used to paginate
//
// See example: examples/user/followers.go
func (user *User) Followers() *Users {
	users := &Users{}
	users.inst = user.inst
	users.endpoint = fmt.Sprintf(urlFollowers, user.ID)
	return users
}

// Block blocks user
//
// This function updates current User.Friendship structure.
//
// See example: examples/user/block.go
func (user *User) Block() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err == nil {
		body, err := insta.sendRequest(
			&reqOptions{
				Endpoint: fmt.Sprintf(urlUserBlock, user.ID),
				Query:    generateSignature(data),
				IsPost:   true,
			},
		)
		if err == nil {
			resp := friendResp{}
			err = json.Unmarshal(body, &resp)
			user.Friendship = resp.Friendship
		}
	}
	return err
}

// Unblock unblocks user
//
// This function updates current User.Friendship structure.
//
// See example: examples/user/unblock.go
func (user *User) Unblock() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err == nil {
		body, err := insta.sendRequest(
			&reqOptions{
				Endpoint: fmt.Sprintf(urlUserUnblock, user.ID),
				Query:    generateSignature(data),
				IsPost:   true,
			},
		)
		if err == nil {
			resp := friendResp{}
			err = json.Unmarshal(body, &resp)
			user.Friendship = resp.Friendship
		}
	}
	return err
}

// Follow started following some user
//
// This function performs a follow call. If user is private
// you have to wait until he/she accepts you.
//
// If the account is public User.Friendship will be updated
//
// See example: examples/user/follow.go
func (user *User) Follow() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err == nil {
		body, err := insta.sendRequest(
			&reqOptions{
				Endpoint: fmt.Sprintf(urlUserFollow, user.ID),
				Query:    generateSignature(data),
				IsPost:   true,
			},
		)
		if err == nil {
			resp := friendResp{}
			err = json.Unmarshal(body, &resp)
			user.Friendship = resp.Friendship
		}
	}
	return err
}

// Unfollow unfollows user
//
// User.Friendship will be updated
//
// See example: examples/user/unfollow.go
func (user *User) Unfollow() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err == nil {
		body, err := insta.sendRequest(
			&reqOptions{
				Endpoint: fmt.Sprintf(urlUserUnfollow, user.ID),
				Query:    generateSignature(data),
				IsPost:   true,
			},
		)
		if err == nil {
			resp := friendResp{}
			err = json.Unmarshal(body, &resp)
			user.Friendship = resp.Friendship
		}
	}
	return err
}

func (user *User) friendShip() error {
	insta := user.inst
	data, err := insta.prepareData(
		map[string]interface{}{
			"user_id": user.ID,
		},
	)
	if err != nil {
		return err
	}

	body, err := insta.sendRequest(
		&reqOptions{
			Endpoint: fmt.Sprintf(urlFriendship, user.ID),
			Query:    generateSignature(data),
			IsPost:   true,
		},
	)
	if err == nil {
		err = json.Unmarshal(body, &user.Friendship)
	}
	return err
}

// Feed returns user feeds (media)
//
// minTime is the minimum timestamp of media.
//
// For pagination use FeedMedia.Next()
//
// See example: examples/user/feed.go
func (user *User) Feed(minTime []byte) *FeedMedia {
	insta := user.inst
	timestamp := b2s(minTime)

	media := &FeedMedia{}
	media.timestamp = timestamp
	media.inst = insta
	media.endpoint = urlUserFeed
	media.uid = user.ID
	return media
}

// Stories returns user stories
//
// Use StoryMedia.Next for pagination.
//
// See example: examples/user/stories.go
func (user *User) Stories() *StoryMedia {
	media := &StoryMedia{}
	media.uid = user.ID
	media.inst = user.inst
	media.endpoint = urlUserStories
	return media
}

// Tags returns media where user is tagged in
//
// For pagination use FeedMedia.Next()
//
// See example: examples/user/tags.go
func (user *User) Tags(minTimestamp []byte) (*FeedMedia, error) {
	timestamp := b2s(minTimestamp)
	body, err := user.inst.sendRequest(
		&reqOptions{
			Endpoint: fmt.Sprintf(urlUserTags, user.ID),
			Query: map[string]string{
				"max_id":         "",
				"rank_token":     user.inst.rankToken,
				"min_timestamp":  timestamp,
				"ranked_content": "true",
			},
		},
	)
	if err != nil {
		return nil, err
	}

	media := &FeedMedia{}
	err = json.Unmarshal(body, media)
	media.inst = user.inst
	media.endpoint = urlUserTags
	media.uid = user.ID
	return media, err
}
<|endoftext|>


<commit_before>package main

import (
    "os"
    "fmt"
    "bufio"
    "strconv"
    "strings"
    "syscall"
)

func usage() {
    fmt.Fprintf(os.Stderr, "Usage: dasc <req fd> <res fd>\n");
}

func getIntArg(i int) int {
    if i < len(os.Args) {
        a, err := strconv.Atoi(os.Args[i])
        if err == nil {
            return a
        }
    }
    usage()
    os.Exit(1)
    return -1
}

func prompt() {
    fmt.Print("> ")
}

func lackeol() {
    fmt.Println("\033[7m%\033[m")
}

func search(path string) string {
    if path[0] == '/' {
        return path
    }
    return "/bin/" + path
}

func readline(stdin *bufio.Reader) (line string, err error) {
    line, err = stdin.ReadString('\n')
    if err == nil {
        line = line[:len(line)-1]
    }
    return
}

func main() {
    InitTube(getIntArg(1), getIntArg(2))

    stdin := bufio.NewReader(os.Stdin)
    devnull, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0)

    if err != nil {
        panic("Failed to open /dev/null")
    }

    env := make(map[string]string)
    for _, e := range os.Environ() {
        arr := strings.SplitN(e, "=", 2)
        if len(arr) == 2 {
            env[arr[0]] = arr[1]
        }
    }

    for {
        prompt()
        line, err := readline(stdin)
        if err != nil {
            lackeol()
            break
        }
        words := strings.Split(line, " ")
        if len(words) == 0 {
            continue
        }
        words[0] = search(words[0])
        cmd := ReqCmd{
            Path: words[0],
            Args: words,
            Env: env,
            // RedirOutput: true,
            Output: devnull,
        }

        SendReq(Req{Cmd: &cmd})

        for {
            res, err := RecvRes()
            if err != nil {
                fmt.Printf("broken response pipe, quitting")
                os.Exit(1)
            } else {
                fmt.Printf("response: %v\n", res)
            }

            if res.ProcState != nil {
                break
            }
        }
    }
}
<commit_msg>Client: Print response with %s which is more useful<commit_after>package main

import (
    "os"
    "fmt"
    "bufio"
    "strconv"
    "strings"
    "syscall"
)

func usage() {
    fmt.Fprintf(os.Stderr, "Usage: dasc <req fd> <res fd>\n");
}

func getIntArg(i int) int {
    if i < len(os.Args) {
        a, err := strconv.Atoi(os.Args[i])
        if err == nil {
            return a
        }
    }
    usage()
    os.Exit(1)
    return -1
}

func prompt() {
    fmt.Print("> ")
}

func lackeol() {
    fmt.Println("\033[7m%\033[m")
}

func search(path string) string {
    if path[0] == '/' {
        return path
    }
    return "/bin/" + path
}

func readline(stdin *bufio.Reader) (line string, err error) {
    line, err = stdin.ReadString('\n')
    if err == nil {
        line = line[:len(line)-1]
    }
    return
}

func main() {
    InitTube(getIntArg(1), getIntArg(2))

    stdin := bufio.NewReader(os.Stdin)
    devnull, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0)

    if err != nil {
        panic("Failed to open /dev/null")
    }

    env := make(map[string]string)
    for _, e := range os.Environ() {
        arr := strings.SplitN(e, "=", 2)
        if len(arr) == 2 {
            env[arr[0]] = arr[1]
        }
    }

    for {
        prompt()
        line, err := readline(stdin)
        if err != nil {
            lackeol()
            break
        }
        words := strings.Split(line, " ")
        if len(words) == 0 {
            continue
        }
        words[0] = search(words[0])
        cmd := ReqCmd{
            Path: words[0],
            Args: words,
            Env: env,
            // RedirOutput: true,
            Output: devnull,
        }

        SendReq(Req{Cmd: &cmd})

        for {
            res, err := RecvRes()
            if err != nil {
                fmt.Printf("broken response pipe, quitting")
                os.Exit(1)
            } else {
                fmt.Printf("response: %s\n", res)
            }

            if res.ProcState != nil {
                break
            }
        }
    }
}
<|endoftext|>


<commit_before>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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 e2e

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"golang.org/x/crypto/ssh"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

// TODO: it would probably be slightly better to build up the objects
// in the code and then serialize to yaml.
var addon_controller_v1 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: addon-test-v1
  namespace: default
  labels:
    k8s-app: addon-test
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 2
  selector:
    k8s-app: addon-test
    version: v1
  template:
    metadata:
      labels:
        k8s-app: addon-test
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var addon_controller_v2 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: addon-test-v2
  namespace: default
  labels:
    k8s-app: addon-test
    version: v2
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 2
  selector:
    k8s-app: addon-test
    version: v2
  template:
    metadata:
      labels:
        k8s-app: addon-test
        version: v2
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var addon_service_v1 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: addon-test
  namespace: default
  labels:
    k8s-app: addon-test
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: addon-test
spec:
  ports:
  - port: 9376
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: addon-test
`

var addon_service_v2 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: addon-test-updated
  namespace: default
  labels:
    k8s-app: addon-test
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: addon-test
    newLabel: newValue
spec:
  ports:
  - port: 9376
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: addon-test
`

var invalid_addon_controller_v1 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: invalid-addon-test-v1
  namespace: default
  labels:
    k8s-app: invalid-addon-test
    version: v1
spec:
  replicas: 2
  selector:
    k8s-app: invalid-addon-test
    version: v1
  template:
    metadata:
      labels:
        k8s-app: invalid-addon-test
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: invalid-addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var invalid_addon_service_v1 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: ivalid-addon-test
  namespace: default
  labels:
    k8s-app: invalid-addon-test
    kubernetes.io/name: invalid-addon-test
spec:
  ports:
  - port: 9377
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: invalid-addon-test
`

var addonTestPollInterval = 3 * time.Second
var addonTestPollTimeout = 1 * time.Minute
var addonNamespace = api.NamespaceDefault // addons are in the default namespace

type stringPair struct {
	data, fileName string
}

var _ = Describe("Addon update", func() {

	var dir string
	var sshClient *ssh.Client
	var c *client.Client
	var namespace *api.Namespace

	BeforeEach(func() {
		var err error
		c, err = loadClient()
		Expect(err).NotTo(HaveOccurred())

		sshClient, err = getSSHClient()
		Expect(err).NotTo(HaveOccurred())

		namespace, err = createTestingNS("addon-update-test", c)
		Expect(err).NotTo(HaveOccurred())

		// do not use "service" command because it clears the environment variables
		sshExecAndVerify(sshClient, "sudo TEST_ADDON_CHECK_INTERVAL_SEC=1 /etc/init.d/kube-addons restart")
	})

	AfterEach(func() {
		if sshClient != nil {
			// restart addon_update with the default options
			sshExec(sshClient, "sudo /etc/init.d/kube-addons restart")
			sshClient.Close()
		}
		if err := c.Namespaces().Delete(namespace.Name); err != nil {
			Failf("Couldn't delete ns %q: %s", namespace, err)
		}
		// Paranoia-- prevent reuse!
		namespace = nil
		c = nil
	})

	// WARNING: the test is not parallel-friendly!
	It("should propagate add-on file changes", func() {
		//these tests are long, so I squeezed several cases in one scenario
		Expect(sshClient).NotTo(BeNil())
		dir = namespace.Name // we use it only to give a unique string for each test execution

		// This test requires SSH, so the provider check should be identical to
		// those tests.
		if !providerIs("gce") {
			Logf(fmt.Sprintf("Skipping test, which is not implemented for %s", testContext.Provider))
			return
		}

		temporaryRemotePathPrefix := "addon-test-dir"
		temporaryRemotePath := temporaryRemotePathPrefix + "/" + dir                  // in home directory on kubernetes-master
		defer sshExec(sshClient, fmt.Sprintf("rm -rf %s", temporaryRemotePathPrefix)) // ignore the result in cleanup
		sshExecAndVerify(sshClient, fmt.Sprintf("mkdir -p %s", temporaryRemotePath))

		rcv1 := "addon-controller-v1.yaml"
		rcv2 := "addon-controller-v2.yaml"
		rcInvalid := "invalid-addon-controller-v1.yaml"

		svcv1 := "addon-service-v1.yaml"
		svcv2 := "addon-service-v2.yaml"
		svcInvalid := "invalid-addon-service-v1.yaml"

		var remoteFiles []stringPair = []stringPair{
			{addon_controller_v1, rcv1},
			{addon_controller_v2, rcv2},
			{addon_service_v1, svcv1},
			{addon_service_v2, svcv2},
			{invalid_addon_controller_v1, rcInvalid},
			{invalid_addon_service_v1, svcInvalid},
		}

		for _, p := range remoteFiles {
			err := writeRemoteFile(sshClient, p.data, temporaryRemotePath, p.fileName, 0644)
			Expect(err).NotTo(HaveOccurred())
		}

		// directory on kubernetes-master
		destinationDirPrefix := "/etc/kubernetes/addons/addon-test-dir"
		destinationDir := destinationDirPrefix + "/" + dir

		// cleanup from previous tests
		_, _, _, err := sshExec(sshClient, fmt.Sprintf("sudo rm -rf %s", destinationDirPrefix))
		Expect(err).NotTo(HaveOccurred())

		defer sshExec(sshClient, fmt.Sprintf("sudo rm -rf %s", destinationDirPrefix)) // ignore result in cleanup
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo mkdir -p %s", destinationDir))

		By("copy invalid manifests to the destination dir (without kubernetes.io/cluster-service label)")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcInvalid, destinationDir, rcInvalid))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcInvalid, destinationDir, svcInvalid))
		// we will verify at the end of the test that the objects weren't created from the invalid manifests

		By("copy new manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv1, destinationDir, rcv1))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcv1, destinationDir, svcv1))

		waitForServiceInAddonTest(c, "addon-test", true)
		waitForReplicationControllerInAddonTest(c, "addon-test-v1", true)

		By("update manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv2, destinationDir, rcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcv2, destinationDir, svcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, rcv1))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, svcv1))
		/**
		 * Note that we have a small race condition here - the kube-addon-updater
		 * May notice that a new rc/service file appeared, while the old one will still be there.
		 * But it is ok - as long as we don't have rolling update, the result will be the same
		 */

		waitForServiceInAddonTest(c, "addon-test-updated", true)
		waitForReplicationControllerInAddonTest(c, "addon-test-v2", true)

		waitForServiceInAddonTest(c, "addon-test", false)
		waitForReplicationControllerInAddonTest(c, "addon-test-v1", false)

		By("remove manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, rcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, svcv2))

		waitForServiceInAddonTest(c, "addon-test-updated", false)
		waitForReplicationControllerInAddonTest(c, "invalid-addon-test-v1", false)

		By("verify invalid API addons weren't created")
		_, err = c.ReplicationControllers(addonNamespace).Get("invalid-addon-test-v1")
		Expect(err).To(HaveOccurred())
		_, err = c.Services(addonNamespace).Get("ivalid-addon-test")
		Expect(err).To(HaveOccurred())

		// invalid addons will be deleted by the deferred function
	})
})

func waitForServiceInAddonTest(c *client.Client, name string, exist bool) {
	expectNoError(waitForService(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout))
}

func waitForReplicationControllerInAddonTest(c *client.Client, name string, exist bool) {
	expectNoError(waitForReplicationController(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout))
}

// TODO marekbiskup 2015-06-11: merge the ssh code into pkg/util/ssh.go after
// kubernetes v1.0 is released. In particular the code of sshExec.
func getSSHClient() (*ssh.Client, error) {
	// Get a signer for the provider.
	signer, err := getSigner(testContext.Provider)
	if err != nil {
		return nil, fmt.Errorf("error getting signer for provider %s: '%v'", testContext.Provider, err)
	}

	config := &ssh.ClientConfig{
		User: os.Getenv("USER"),
		Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
	}

	host := getMasterHost() + ":22"
	client, err := ssh.Dial("tcp", host, config)
	if err != nil {
		return nil, fmt.Errorf("error getting SSH client to host %s: '%v'", host, err)
	}
	return client, err
}

func sshExecAndVerify(client *ssh.Client, cmd string) {
	_, _, rc, err := sshExec(client, cmd)
	Expect(err).NotTo(HaveOccurred())
	Expect(rc).To(Equal(0))
}

func sshExec(client *ssh.Client, cmd string) (string, string, int, error) {
	Logf(fmt.Sprintf("Executing '%s' on %v", cmd, client.RemoteAddr()))
	session, err := client.NewSession()
	if err != nil {
		return "", "", 0, fmt.Errorf("error creating session to host %s: '%v'", client.RemoteAddr(), err)
	}
	defer session.Close()

	// Run the command.
	code := 0
	var bout, berr bytes.Buffer

	session.Stdout, session.Stderr = &bout, &berr
	err = session.Run(cmd)
	if err != nil {
		// Check whether the command failed to run or didn't complete.
		if exiterr, ok := err.(*ssh.ExitError); ok {
			// If we got an ExitError and the exit code is nonzero, we'll
			// consider the SSH itself successful (just that the command run
			// errored on the host).
			if code = exiterr.ExitStatus(); code != 0 {
				err = nil
			}
		} else {
			// Some other kind of error happened (e.g. an IOError); consider the
			// SSH unsuccessful.
			err = fmt.Errorf("failed running `%s` on %s: '%v'", cmd, client.RemoteAddr(), err)
		}
	}
	return bout.String(), berr.String(), code, err
}

func writeRemoteFile(sshClient *ssh.Client, data, dir, fileName string, mode os.FileMode) error {
	Logf(fmt.Sprintf("Writing remote file '%s/%s' on %v", dir, fileName, sshClient.RemoteAddr()))
	session, err := sshClient.NewSession()
	if err != nil {
		return fmt.Errorf("error creating session to host %s: '%v'", sshClient.RemoteAddr(), err)
	}
	defer session.Close()

	fileSize := len(data)
	go func() {
		// ignore errors here. scp whould return errors if something goes wrong.
		pipe, _ := session.StdinPipe()
		defer pipe.Close()
		fmt.Fprintf(pipe, "C%#o %d %s\n", mode, fileSize, fileName)
		io.Copy(pipe, strings.NewReader(data))
		fmt.Fprint(pipe, "\x00")
	}()
	if err := session.Run(fmt.Sprintf("scp -t %s", dir)); err != nil {
		return err
	}
	return nil
}
<commit_msg>timeout in addon_update e2e test increased<commit_after>/*
Copyright 2015 The Kubernetes Authors All rights reserved.

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 e2e

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"golang.org/x/crypto/ssh"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

// TODO: it would probably be slightly better to build up the objects
// in the code and then serialize to yaml.
var addon_controller_v1 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: addon-test-v1
  namespace: default
  labels:
    k8s-app: addon-test
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 2
  selector:
    k8s-app: addon-test
    version: v1
  template:
    metadata:
      labels:
        k8s-app: addon-test
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var addon_controller_v2 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: addon-test-v2
  namespace: default
  labels:
    k8s-app: addon-test
    version: v2
    kubernetes.io/cluster-service: "true"
spec:
  replicas: 2
  selector:
    k8s-app: addon-test
    version: v2
  template:
    metadata:
      labels:
        k8s-app: addon-test
        version: v2
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var addon_service_v1 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: addon-test
  namespace: default
  labels:
    k8s-app: addon-test
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: addon-test
spec:
  ports:
  - port: 9376
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: addon-test
`

var addon_service_v2 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: addon-test-updated
  namespace: default
  labels:
    k8s-app: addon-test
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: addon-test
    newLabel: newValue
spec:
  ports:
  - port: 9376
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: addon-test
`

var invalid_addon_controller_v1 = `
apiVersion: v1beta3
kind: ReplicationController
metadata:
  name: invalid-addon-test-v1
  namespace: default
  labels:
    k8s-app: invalid-addon-test
    version: v1
spec:
  replicas: 2
  selector:
    k8s-app: invalid-addon-test
    version: v1
  template:
    metadata:
      labels:
        k8s-app: invalid-addon-test
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      containers:
      - image: gcr.io/google_containers/serve_hostname:1.1
        name: invalid-addon-test
        ports:
        - containerPort: 9376
          protocol: TCP
`

var invalid_addon_service_v1 = `
apiVersion: v1beta3
kind: Service
metadata:
  name: ivalid-addon-test
  namespace: default
  labels:
    k8s-app: invalid-addon-test
    kubernetes.io/name: invalid-addon-test
spec:
  ports:
  - port: 9377
    protocol: TCP
    targetPort: 9376
  selector:
    k8s-app: invalid-addon-test
`

var addonTestPollInterval = 3 * time.Second
var addonTestPollTimeout = 3 * time.Minute
var addonNamespace = api.NamespaceDefault // addons are in the default namespace

type stringPair struct {
	data, fileName string
}

var _ = Describe("Addon update", func() {

	var dir string
	var sshClient *ssh.Client
	var c *client.Client
	var namespace *api.Namespace

	BeforeEach(func() {
		var err error
		c, err = loadClient()
		Expect(err).NotTo(HaveOccurred())

		sshClient, err = getSSHClient()
		Expect(err).NotTo(HaveOccurred())

		namespace, err = createTestingNS("addon-update-test", c)
		Expect(err).NotTo(HaveOccurred())

		// do not use "service" command because it clears the environment variables
		sshExecAndVerify(sshClient, "sudo TEST_ADDON_CHECK_INTERVAL_SEC=1 /etc/init.d/kube-addons restart")
	})

	AfterEach(func() {
		if sshClient != nil {
			// restart addon_update with the default options
			sshExec(sshClient, "sudo /etc/init.d/kube-addons restart")
			sshClient.Close()
		}
		if err := c.Namespaces().Delete(namespace.Name); err != nil {
			Failf("Couldn't delete ns %q: %s", namespace, err)
		}
		// Paranoia-- prevent reuse!
		namespace = nil
		c = nil
	})

	// WARNING: the test is not parallel-friendly!
	It("should propagate add-on file changes", func() {
		//these tests are long, so I squeezed several cases in one scenario
		Expect(sshClient).NotTo(BeNil())
		dir = namespace.Name // we use it only to give a unique string for each test execution

		// This test requires SSH, so the provider check should be identical to
		// those tests.
		if !providerIs("gce") {
			Logf(fmt.Sprintf("Skipping test, which is not implemented for %s", testContext.Provider))
			return
		}

		temporaryRemotePathPrefix := "addon-test-dir"
		temporaryRemotePath := temporaryRemotePathPrefix + "/" + dir                  // in home directory on kubernetes-master
		defer sshExec(sshClient, fmt.Sprintf("rm -rf %s", temporaryRemotePathPrefix)) // ignore the result in cleanup
		sshExecAndVerify(sshClient, fmt.Sprintf("mkdir -p %s", temporaryRemotePath))

		rcv1 := "addon-controller-v1.yaml"
		rcv2 := "addon-controller-v2.yaml"
		rcInvalid := "invalid-addon-controller-v1.yaml"

		svcv1 := "addon-service-v1.yaml"
		svcv2 := "addon-service-v2.yaml"
		svcInvalid := "invalid-addon-service-v1.yaml"

		var remoteFiles []stringPair = []stringPair{
			{addon_controller_v1, rcv1},
			{addon_controller_v2, rcv2},
			{addon_service_v1, svcv1},
			{addon_service_v2, svcv2},
			{invalid_addon_controller_v1, rcInvalid},
			{invalid_addon_service_v1, svcInvalid},
		}

		for _, p := range remoteFiles {
			err := writeRemoteFile(sshClient, p.data, temporaryRemotePath, p.fileName, 0644)
			Expect(err).NotTo(HaveOccurred())
		}

		// directory on kubernetes-master
		destinationDirPrefix := "/etc/kubernetes/addons/addon-test-dir"
		destinationDir := destinationDirPrefix + "/" + dir

		// cleanup from previous tests
		_, _, _, err := sshExec(sshClient, fmt.Sprintf("sudo rm -rf %s", destinationDirPrefix))
		Expect(err).NotTo(HaveOccurred())

		defer sshExec(sshClient, fmt.Sprintf("sudo rm -rf %s", destinationDirPrefix)) // ignore result in cleanup
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo mkdir -p %s", destinationDir))

		By("copy invalid manifests to the destination dir (without kubernetes.io/cluster-service label)")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcInvalid, destinationDir, rcInvalid))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcInvalid, destinationDir, svcInvalid))
		// we will verify at the end of the test that the objects weren't created from the invalid manifests

		By("copy new manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv1, destinationDir, rcv1))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcv1, destinationDir, svcv1))

		waitForServiceInAddonTest(c, "addon-test", true)
		waitForReplicationControllerInAddonTest(c, "addon-test-v1", true)

		By("update manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, rcv2, destinationDir, rcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo cp %s/%s %s/%s", temporaryRemotePath, svcv2, destinationDir, svcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, rcv1))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, svcv1))
		/**
		 * Note that we have a small race condition here - the kube-addon-updater
		 * May notice that a new rc/service file appeared, while the old one will still be there.
		 * But it is ok - as long as we don't have rolling update, the result will be the same
		 */

		waitForServiceInAddonTest(c, "addon-test-updated", true)
		waitForReplicationControllerInAddonTest(c, "addon-test-v2", true)

		waitForServiceInAddonTest(c, "addon-test", false)
		waitForReplicationControllerInAddonTest(c, "addon-test-v1", false)

		By("remove manifests")
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, rcv2))
		sshExecAndVerify(sshClient, fmt.Sprintf("sudo rm %s/%s", destinationDir, svcv2))

		waitForServiceInAddonTest(c, "addon-test-updated", false)
		waitForReplicationControllerInAddonTest(c, "invalid-addon-test-v1", false)

		By("verify invalid API addons weren't created")
		_, err = c.ReplicationControllers(addonNamespace).Get("invalid-addon-test-v1")
		Expect(err).To(HaveOccurred())
		_, err = c.Services(addonNamespace).Get("ivalid-addon-test")
		Expect(err).To(HaveOccurred())

		// invalid addons will be deleted by the deferred function
	})
})

func waitForServiceInAddonTest(c *client.Client, name string, exist bool) {
	expectNoError(waitForService(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout))
}

func waitForReplicationControllerInAddonTest(c *client.Client, name string, exist bool) {
	expectNoError(waitForReplicationController(c, addonNamespace, name, exist, addonTestPollInterval, addonTestPollTimeout))
}

// TODO marekbiskup 2015-06-11: merge the ssh code into pkg/util/ssh.go after
// kubernetes v1.0 is released. In particular the code of sshExec.
func getSSHClient() (*ssh.Client, error) {
	// Get a signer for the provider.
	signer, err := getSigner(testContext.Provider)
	if err != nil {
		return nil, fmt.Errorf("error getting signer for provider %s: '%v'", testContext.Provider, err)
	}

	config := &ssh.ClientConfig{
		User: os.Getenv("USER"),
		Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)},
	}

	host := getMasterHost() + ":22"
	client, err := ssh.Dial("tcp", host, config)
	if err != nil {
		return nil, fmt.Errorf("error getting SSH client to host %s: '%v'", host, err)
	}
	return client, err
}

func sshExecAndVerify(client *ssh.Client, cmd string) {
	_, _, rc, err := sshExec(client, cmd)
	Expect(err).NotTo(HaveOccurred())
	Expect(rc).To(Equal(0))
}

func sshExec(client *ssh.Client, cmd string) (string, string, int, error) {
	Logf(fmt.Sprintf("Executing '%s' on %v", cmd, client.RemoteAddr()))
	session, err := client.NewSession()
	if err != nil {
		return "", "", 0, fmt.Errorf("error creating session to host %s: '%v'", client.RemoteAddr(), err)
	}
	defer session.Close()

	// Run the command.
	code := 0
	var bout, berr bytes.Buffer

	session.Stdout, session.Stderr = &bout, &berr
	err = session.Run(cmd)
	if err != nil {
		// Check whether the command failed to run or didn't complete.
		if exiterr, ok := err.(*ssh.ExitError); ok {
			// If we got an ExitError and the exit code is nonzero, we'll
			// consider the SSH itself successful (just that the command run
			// errored on the host).
			if code = exiterr.ExitStatus(); code != 0 {
				err = nil
			}
		} else {
			// Some other kind of error happened (e.g. an IOError); consider the
			// SSH unsuccessful.
			err = fmt.Errorf("failed running `%s` on %s: '%v'", cmd, client.RemoteAddr(), err)
		}
	}
	return bout.String(), berr.String(), code, err
}

func writeRemoteFile(sshClient *ssh.Client, data, dir, fileName string, mode os.FileMode) error {
	Logf(fmt.Sprintf("Writing remote file '%s/%s' on %v", dir, fileName, sshClient.RemoteAddr()))
	session, err := sshClient.NewSession()
	if err != nil {
		return fmt.Errorf("error creating session to host %s: '%v'", sshClient.RemoteAddr(), err)
	}
	defer session.Close()

	fileSize := len(data)
	go func() {
		// ignore errors here. scp whould return errors if something goes wrong.
		pipe, _ := session.StdinPipe()
		defer pipe.Close()
		fmt.Fprintf(pipe, "C%#o %d %s\n", mode, fileSize, fileName)
		io.Copy(pipe, strings.NewReader(data))
		fmt.Fprint(pipe, "\x00")
	}()
	if err := session.Run(fmt.Sprintf("scp -t %s", dir)); err != nil {
		return err
	}
	return nil
}
<|endoftext|>