<commit_before>// Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
// Licensed under the Mozilla Public License v2.0

package oci

import (
	"fmt"
	"regexp"
	"testing"

	"github.com/terraform-providers/terraform-provider-oci/httpreplay"

	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
	"github.com/oracle/oci-go-sdk/v37/identity"
	"github.com/stretchr/testify/suite"
)

type ResourceIdentityPolicyTestSuite struct {
	suite.Suite
	Providers      map[string]terraform.ResourceProvider
	Config         string
	ResourceName   string
	DataSourceName string
	Token          string
	TokenFn        func(string, map[string]string) string
}

func (s *ResourceIdentityPolicyTestSuite) SetupTest() {
	s.Token, s.TokenFn = tokenizeWithHttpReplay("identity_policy")
	s.Providers = testAccProviders
	testAccPreCheck(s.T())
	s.Config = legacyTestProviderConfig() + s.TokenFn(`
	resource "oci_identity_group" "t" {
		name = "{{.token}}"
		description = "automated test group"
		compartment_id = "${var.tenancy_ocid}"
	}`, nil)
	s.ResourceName = "oci_identity_policy.p"
	s.DataSourceName = "data.oci_identity_policies.p"
}

func (s *ResourceIdentityPolicyTestSuite) TestAccResourceIdentityPolicy_basic() {
	var policyHash string
	resource.Test(s.T(), resource.TestCase{
		Providers: s.Providers,
		Steps: []resource.TestStep{
			// verify create
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "p1-{{.token}}"
					description = "automated test policy"
					version_date = "2018-04-17"
					statements = ["Allow group ${oci_identity_group.t.name} to read instances in tenancy"]
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(s.ResourceName, "id"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "compartment_id"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "time_created"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "ETag"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "lastUpdateETag"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "policyHash"),
					resource.TestCheckResourceAttr(s.ResourceName, "name", "p1-"+s.Token),
					resource.TestCheckResourceAttr(s.ResourceName, "description", "automated test policy"),
					resource.TestCheckResourceAttr(s.ResourceName, "statements.#", "1"),
					resource.TestCheckResourceAttr(s.ResourceName, "state", string(identity.PolicyLifecycleStateActive)),
					resource.TestCheckResourceAttr(s.ResourceName, "version_date", "2018-04-17"),
					resource.TestCheckNoResourceAttr(s.ResourceName, "inactive_state"),
					func(s *terraform.State) (err error) {
						policyHash, err = fromInstanceState(s, "oci_identity_policy.p", "policyHash")
						return err
					},
				),
			},
			// verify update
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "{{.token}}"
					description = "automated test policy (updated)"
					version_date = "2018-04-18"
					statements = [
						"Allow group ${oci_identity_group.t.name} to inspect instances in tenancy",
						"Allow group ${oci_identity_group.t.name} to read instances in tenancy"
					]
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(s.ResourceName, "name", s.Token),
					resource.TestCheckResourceAttr(s.ResourceName, "description", "automated test policy (updated)"),
					resource.TestCheckResourceAttr(s.ResourceName, "version_date", "2018-04-18"),
					resource.TestCheckResourceAttr(s.ResourceName, "statements.#", "2"),
					func(s *terraform.State) (err error) {
						newHash, err := fromInstanceState(s, "oci_identity_policy.p", "policyHash")
						if policyHash == newHash {
							return fmt.Errorf("Expected new hash, got same")
						}
						return err
					},
				),
			},
			// verify data source, + filtering against array of items
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "{{.token}}"
					description = "automated test policy (updated)"
					version_date = "2018-04-18"
					statements = [
						"Allow group ${oci_identity_group.t.name} to inspect instances in tenancy",
						"Allow group ${oci_identity_group.t.name} to read instances in tenancy"
					]
				}
				data "oci_identity_policies" "p" {
					compartment_id = "${var.tenancy_ocid}"
					filter {
						name   = "statements"
						values = ["Allow group ${oci_identity_group.t.name} to inspect instances in tenancy"]
					}
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.#", "1"),
					resource.TestCheckResourceAttrSet(s.DataSourceName, "policies.0.id"),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.name", s.Token),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.description", "automated test policy (updated)"),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.state", string(identity.PolicyLifecycleStateActive)),
					// TODO: This field is not being returned by the service call but is still showing up in the datasource
					// resource.TestCheckNoResourceAttr(s.ResourceName, "policies.0.inactive_state"),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.statements.#", "2"),
					resource.TestCheckResourceAttrSet(s.DataSourceName, "policies.0.time_created"),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.version_date", "2018-04-18"),
				),
			},
			// verify resource import
			{
				Config:                  s.Config,
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{},
				ResourceName:            s.ResourceName,
			},
		},
	},
	)
}

func (s *ResourceIdentityPolicyTestSuite) TestAccResourceIdentityPolicy_emptyStatement() {
	resource.Test(s.T(), resource.TestCase{
		Providers: s.Providers,
		Steps: []resource.TestStep{
			// verify create
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "p1-{{.token}}"
					description = "automated test policy"
					version_date = "2018-04-17"
					statements = [
"Allow group ${oci_identity_group.t.name} to inspect instances in tenancy",
"",
"Allow group ${oci_identity_group.t.name} to inspect instances in tenancy"]
				}`, nil),
				ExpectError: regexp.MustCompile("Service error:InvalidParameter"),
			},
		},
	},
	)
}

func (s *ResourceIdentityPolicyTestSuite) TestAccResourceIdentityPolicy_formattingDiff() {
	var lastUpdateETag, policyHash string
	resource.Test(s.T(), resource.TestCase{
		Providers: s.Providers,
		Steps: []resource.TestStep{
			// create policy with bad formatting
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "{{.token}}"
					description = "automated test policy"
					statements = ["Allow group ${oci_identity_group.t.name} to read instances in >> tenancy"]
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					// policy statements may or may not have invalid characters stripped (">>" above), accommodate this uncertainty as specifically as possible
					resource.TestMatchResourceAttr(s.ResourceName, "statements.0",
						regexp.MustCompile(`Allow group `+s.Token+` to read instances in (>> )?tenancy`)),
					func(s *terraform.State) (err error) {
						if policyHash, err = fromInstanceState(s, "oci_identity_policy.p", "policyHash"); err == nil {
							lastUpdateETag, err = fromInstanceState(s, "oci_identity_policy.p", "lastUpdateETag")
						}
						return err
					},
				),
			},
			// verify update does not change the hash and ETag value
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "{{.token}}"
					description = "automated test policy"
					statements = ["Allow group ${oci_identity_group.t.name} to read instances in >> tenancy"]
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					func(s *terraform.State) (err error) {
						resource.TestCheckResourceAttr("oci_identity_policy.p", "policyHash", policyHash)
						resource.TestCheckResourceAttr("oci_identity_policy.p", "lastUpdateETag", lastUpdateETag)
						return err
					},
				),
			},
		},
	},
	)
}

func TestResourceIdentityPolicyTestSuite(t *testing.T) {
	httpreplay.SetScenario("TestResourceIdentityPolicyTestSuite")
	defer httpreplay.SaveScenario()
	suite.Run(t, new(ResourceIdentityPolicyTestSuite))
}
<commit_msg>test fix for identity policy emptyStatement<commit_after>// Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
// Licensed under the Mozilla Public License v2.0

package oci

import (
	"fmt"
	"regexp"
	"testing"

	"github.com/terraform-providers/terraform-provider-oci/httpreplay"

	"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
	"github.com/hashicorp/terraform-plugin-sdk/terraform"
	"github.com/oracle/oci-go-sdk/v37/identity"
	"github.com/stretchr/testify/suite"
)

type ResourceIdentityPolicyTestSuite struct {
	suite.Suite
	Providers      map[string]terraform.ResourceProvider
	Config         string
	ResourceName   string
	DataSourceName string
	Token          string
	TokenFn        func(string, map[string]string) string
}

func (s *ResourceIdentityPolicyTestSuite) SetupTest() {
	s.Token, s.TokenFn = tokenizeWithHttpReplay("identity_policy")
	s.Providers = testAccProviders
	testAccPreCheck(s.T())
	s.Config = legacyTestProviderConfig() + s.TokenFn(`
	resource "oci_identity_group" "t" {
		name = "{{.token}}"
		description = "automated test group"
		compartment_id = "${var.tenancy_ocid}"
	}`, nil)
	s.ResourceName = "oci_identity_policy.p"
	s.DataSourceName = "data.oci_identity_policies.p"
}

func (s *ResourceIdentityPolicyTestSuite) TestAccResourceIdentityPolicy_basic() {
	var policyHash string
	resource.Test(s.T(), resource.TestCase{
		Providers: s.Providers,
		Steps: []resource.TestStep{
			// verify create
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "p1-{{.token}}"
					description = "automated test policy"
					version_date = "2018-04-17"
					statements = ["Allow group ${oci_identity_group.t.name} to read instances in tenancy"]
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttrSet(s.ResourceName, "id"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "compartment_id"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "time_created"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "ETag"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "lastUpdateETag"),
					resource.TestCheckResourceAttrSet(s.ResourceName, "policyHash"),
					resource.TestCheckResourceAttr(s.ResourceName, "name", "p1-"+s.Token),
					resource.TestCheckResourceAttr(s.ResourceName, "description", "automated test policy"),
					resource.TestCheckResourceAttr(s.ResourceName, "statements.#", "1"),
					resource.TestCheckResourceAttr(s.ResourceName, "state", string(identity.PolicyLifecycleStateActive)),
					resource.TestCheckResourceAttr(s.ResourceName, "version_date", "2018-04-17"),
					resource.TestCheckNoResourceAttr(s.ResourceName, "inactive_state"),
					func(s *terraform.State) (err error) {
						policyHash, err = fromInstanceState(s, "oci_identity_policy.p", "policyHash")
						return err
					},
				),
			},
			// verify update
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "{{.token}}"
					description = "automated test policy (updated)"
					version_date = "2018-04-18"
					statements = [
						"Allow group ${oci_identity_group.t.name} to inspect instances in tenancy",
						"Allow group ${oci_identity_group.t.name} to read instances in tenancy"
					]
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(s.ResourceName, "name", s.Token),
					resource.TestCheckResourceAttr(s.ResourceName, "description", "automated test policy (updated)"),
					resource.TestCheckResourceAttr(s.ResourceName, "version_date", "2018-04-18"),
					resource.TestCheckResourceAttr(s.ResourceName, "statements.#", "2"),
					func(s *terraform.State) (err error) {
						newHash, err := fromInstanceState(s, "oci_identity_policy.p", "policyHash")
						if policyHash == newHash {
							return fmt.Errorf("Expected new hash, got same")
						}
						return err
					},
				),
			},
			// verify data source, + filtering against array of items
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "{{.token}}"
					description = "automated test policy (updated)"
					version_date = "2018-04-18"
					statements = [
						"Allow group ${oci_identity_group.t.name} to inspect instances in tenancy",
						"Allow group ${oci_identity_group.t.name} to read instances in tenancy"
					]
				}
				data "oci_identity_policies" "p" {
					compartment_id = "${var.tenancy_ocid}"
					filter {
						name   = "statements"
						values = ["Allow group ${oci_identity_group.t.name} to inspect instances in tenancy"]
					}
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.#", "1"),
					resource.TestCheckResourceAttrSet(s.DataSourceName, "policies.0.id"),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.name", s.Token),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.description", "automated test policy (updated)"),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.state", string(identity.PolicyLifecycleStateActive)),
					// TODO: This field is not being returned by the service call but is still showing up in the datasource
					// resource.TestCheckNoResourceAttr(s.ResourceName, "policies.0.inactive_state"),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.statements.#", "2"),
					resource.TestCheckResourceAttrSet(s.DataSourceName, "policies.0.time_created"),
					resource.TestCheckResourceAttr(s.DataSourceName, "policies.0.version_date", "2018-04-18"),
				),
			},
			// verify resource import
			{
				Config:                  s.Config,
				ImportState:             true,
				ImportStateVerify:       true,
				ImportStateVerifyIgnore: []string{},
				ResourceName:            s.ResourceName,
			},
		},
	},
	)
}

func (s *ResourceIdentityPolicyTestSuite) TestAccResourceIdentityPolicy_emptyStatement() {
	resource.Test(s.T(), resource.TestCase{
		Providers: s.Providers,
		Steps: []resource.TestStep{
			// verify create
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "p1-{{.token}}"
					description = "automated test policy"
					version_date = "2018-04-17"
					statements = [
"Allow group ${oci_identity_group.t.name} to inspect instances in tenancy",
"",
"Allow group ${oci_identity_group.t.name} to inspect instances in tenancy"]
				}`, nil),
				ExpectError: regexp.MustCompile("InvalidParameter"),
			},
		},
	},
	)
}

func (s *ResourceIdentityPolicyTestSuite) TestAccResourceIdentityPolicy_formattingDiff() {
	var lastUpdateETag, policyHash string
	resource.Test(s.T(), resource.TestCase{
		Providers: s.Providers,
		Steps: []resource.TestStep{
			// create policy with bad formatting
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "{{.token}}"
					description = "automated test policy"
					statements = ["Allow group ${oci_identity_group.t.name} to read instances in >> tenancy"]
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					// policy statements may or may not have invalid characters stripped (">>" above), accommodate this uncertainty as specifically as possible
					resource.TestMatchResourceAttr(s.ResourceName, "statements.0",
						regexp.MustCompile(`Allow group `+s.Token+` to read instances in (>> )?tenancy`)),
					func(s *terraform.State) (err error) {
						if policyHash, err = fromInstanceState(s, "oci_identity_policy.p", "policyHash"); err == nil {
							lastUpdateETag, err = fromInstanceState(s, "oci_identity_policy.p", "lastUpdateETag")
						}
						return err
					},
				),
			},
			// verify update does not change the hash and ETag value
			{
				Config: s.Config + s.TokenFn(`
				resource "oci_identity_policy" "p" {
					compartment_id = "${var.tenancy_ocid}"
					name = "{{.token}}"
					description = "automated test policy"
					statements = ["Allow group ${oci_identity_group.t.name} to read instances in >> tenancy"]
				}`, nil),
				Check: resource.ComposeAggregateTestCheckFunc(
					func(s *terraform.State) (err error) {
						resource.TestCheckResourceAttr("oci_identity_policy.p", "policyHash", policyHash)
						resource.TestCheckResourceAttr("oci_identity_policy.p", "lastUpdateETag", lastUpdateETag)
						return err
					},
				),
			},
		},
	},
	)
}

func TestResourceIdentityPolicyTestSuite(t *testing.T) {
	httpreplay.SetScenario("TestResourceIdentityPolicyTestSuite")
	defer httpreplay.SaveScenario()
	suite.Run(t, new(ResourceIdentityPolicyTestSuite))
}
<|endoftext|>


<commit_before>package main

import (
	// "errors"
	"fmt"
	gitc "git_comment"
	kp "gopkg.in/alecthomas/kingpin.v2"
	"io"
	"os"
	"os/exec"
	"strconv"
	"strings"
)

const (
	errorPrefix = "git-comment-log"
)

var (
	buildVersion string
	app          = kp.New("git-comment-log", "List git commit comments")
	revision     = app.Arg("revision range", "Filter comments to comments on commits from the specified range").String()
)

func main() {
	app.Version(buildVersion)
	kp.MustParse(app.Parse(os.Args[1:]))
	pwd, err := os.Getwd()
	app.FatalIfError(err, errorPrefix)
	comments, err := gitc.CommentsOnCommit(pwd, revision)
	app.FatalIfError(err, errorPrefix)
	var lineCount int = 0
	if lineEnv := getEnv("LINES"); lineEnv != nil {
		lines, _ := strconv.ParseInt(*lineEnv, 10, 0)
		lineCount = int(lines)
	}
	var usePager bool = lineCount == 0
	var content []byte
	var writer io.WriteCloser
	var cmd *exec.Cmd
	for i := 0; i < len(comments); i++ {
		comment := comments[i]
		formatted := formattedContent(comment)
		if !usePager {
			content = append(content, formatted...)
			lines := strings.Split(string(content), "\n")
			usePager = len(lines) > lineCount-1
		}
		if usePager {
			if writer == nil {
				cmd, writer, err = execPager(pwd)
				app.FatalIfError(err, errorPrefix)
			}
			_, err = writer.Write(formatted)
			app.FatalIfError(err, errorPrefix)
		}
	}
	if writer != nil {
		writer.Close()
		cmd.Wait()
	} else {
		fmt.Println(string(content))
	}
}

func formattedContent(comment *gitc.Comment) []byte {
	if comment.ID != nil && len(*comment.ID) > 0 {
		return []byte(fmt.Sprintf("comment %v\n%v", *comment.ID, comment.Serialize()))
	}
	return []byte(comment.Serialize())
}

func execPager(pwd string) (*exec.Cmd, io.WriteCloser, error) {
	pager := gitc.ConfiguredPager(pwd)
	cmd := exec.Command(*pager)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	pipe, err := cmd.StdinPipe()
	if err != nil {
		return nil, nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}
	return cmd, pipe, nil
}

func getEnv(name string) *string {
	if env := os.Getenv(name); len(env) > 0 {
		return &env
	}
	return nil
}
<commit_msg>Fix unpaged log view<commit_after>package main

import (
	"fmt"
	gitc "git_comment"
	kp "gopkg.in/alecthomas/kingpin.v2"
	"io"
	"os"
	"os/exec"
	"strings"
	"syscall"
	"unsafe"
)

const (
	errorPrefix = "git-comment-log"
)

var (
	buildVersion string
	app          = kp.New("git-comment-log", "List git commit comments")
	revision     = app.Arg("revision range", "Filter comments to comments on commits from the specified range").String()
)

func main() {
	app.Version(buildVersion)
	kp.MustParse(app.Parse(os.Args[1:]))
	pwd, err := os.Getwd()
	app.FatalIfError(err, "pwd")
	showComments(pwd)
}

func showComments(pwd string) {
	comments, err := gitc.CommentsOnCommit(pwd, revision)
	app.FatalIfError(err, "git")
	lineCount := calculateLineCount()
	var usePager bool = lineCount == 0
	var content []byte
	var writer io.WriteCloser
	var cmd *exec.Cmd
	for i := 0; i < len(comments); i++ {
		comment := comments[i]
		formatted := formattedContent(comment)
		if !usePager {
			content = append(content, formatted...)
			lines := strings.Split(string(content), "\n")
			usePager = len(lines) > lineCount-1
		}
		if usePager {
			if writer == nil {
				cmd, writer, err = execPager(pwd)
				app.FatalIfError(err, "pager")
			}
			if len(content) > 0 {
				_, err = writer.Write(content)
				content = []byte{}
			} else {
				_, err = writer.Write(formatted)
			}
			app.FatalIfError(err, "writer")
		}
	}
	if writer != nil {
		writer.Close()
		cmd.Wait()
	} else {
		fmt.Println(string(content))
	}
}

func formattedContent(comment *gitc.Comment) []byte {
	if comment.ID != nil && len(*comment.ID) > 0 {
		return []byte(fmt.Sprintf("comment %v\n%v", *comment.ID, comment.Serialize()))
	}
	return []byte(comment.Serialize())
}

func execPager(pwd string) (*exec.Cmd, io.WriteCloser, error) {
	pager := gitc.ConfiguredPager(pwd)
	cmd := exec.Command(*pager)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	pipe, err := cmd.StdinPipe()
	if err != nil {
		return nil, nil, err
	}
	if err := cmd.Start(); err != nil {
		return nil, nil, err
	}
	return cmd, pipe, nil
}

func calculateLineCount() int {
	var dimensions [4]uint16
	if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, 2, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 {
		return 0
	}
	return int(dimensions[0])
}

func getEnv(name string) *string {
	if env := os.Getenv(name); len(env) > 0 {
		return &env
	}
	return nil
}
<|endoftext|>


<commit_before>package pingdom

import (
	"fmt"
	"sort"
	"strconv"
)

// HttpCheck represents a Pingdom http check.
type HttpCheck struct {
	Name                     string            `json:"name"`
	Hostname                 string            `json:"hostname,omitempty"`
	Resolution               int               `json:"resolution,omitempty"`
	Paused                   bool              `json:"paused,omitempty"`
	SendNotificationWhenDown int               `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int               `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool              `json:"notifywhenbackup,omitempty"`
	Url                      string            `json:"url,omitempty"`
	Encryption               bool              `json:"encryption,omitempty"`
	Port                     int               `json:"port,omitempty"`
	Username                 string            `json:"username,omitempty"`
	Password                 string            `json:"password,omitempty"`
	ShouldContain            string            `json:"shouldcontain,omitempty"`
	ShouldNotContain         string            `json:"shouldnotcontain,omitempty"`
	PostData                 string            `json:"postdata,omitempty"`
	RequestHeaders           map[string]string `json:"requestheaders,omitempty"`
	IntegrationIds           []int             `json:"integrationids,omitempty"`
	Tags                     string            `json:"tags,omitempty"`
	ProbeFilters             string            `json:"probe_filters,omitempty"`
	UserIds                  []int             `json:"userids,omitempty"`
	TeamIds                  []int             `json:"teamids,omitempty"`
}

// PingCheck represents a Pingdom ping check
type PingCheck struct {
	Name                     string `json:"name"`
	Hostname                 string `json:"hostname,omitempty"`
	Resolution               int    `json:"resolution,omitempty"`
	Paused                   bool   `json:"paused,omitempty"`
	SendNotificationWhenDown int    `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int    `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool   `json:"notifywhenbackup,omitempty"`
	IntegrationIds           []int  `json:"integrationids,omitempty"`
	ProbeFilters             string `json:"probe_filters,omitempty"`
	UserIds                  []int  `json:"userids,omitempty"`
	TeamIds                  []int  `json:"teamids,omitempty"`
}

// TCPCheck represents a Pingdom TCP check
type TCPCheck struct {
	Name                     string `json:"name"`
	Hostname                 string `json:"hostname,omitempty"`
	Resolution               int    `json:"resolution,omitempty"`
	Paused                   bool   `json:"paused,omitempty"`
	SendNotificationWhenDown int    `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int    `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool   `json:"notifywhenbackup,omitempty"`
	IntegrationIds           []int  `json:"integrationids,omitempty"`
	ProbeFilters             string `json:"probe_filters,omitempty"`
	UserIds                  []int  `json:"userids,omitempty"`
	TeamIds                  []int  `json:"teamids,omitempty"`
	Port                     int    `json:"port"`
	StringToSend             string `json:"stringtosend,omitempty"`
	StringToExpect           string `json:"stringtoexpect,omitempty"`
}

// Params returns a map of parameters for an HttpCheck that can be sent along
// with an HTTP PUT request
func (ck *HttpCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"url":              ck.Url,
		"encryption":       strconv.FormatBool(ck.Encryption),
		"postdata":         ck.PostData,
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"tags":             ck.Tags,
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
	}

	// Ignore zero values
	if ck.Port != 0 {
		m["port"] = strconv.Itoa(ck.Port)
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	// ShouldContain and ShouldNotContain are mutually exclusive.
	// But we must define one so they can be emptied if required.
	if ck.ShouldContain != "" {
		m["shouldcontain"] = ck.ShouldContain
	} else {
		m["shouldnotcontain"] = ck.ShouldNotContain
	}

	// Convert auth
	if ck.Username != "" {
		m["auth"] = fmt.Sprintf("%s:%s", ck.Username, ck.Password)
	}

	// Convert headers
	var headers []string
	for k := range ck.RequestHeaders {
		headers = append(headers, k)
	}
	sort.Strings(headers)
	for i, k := range headers {
		m[fmt.Sprintf("requestheader%d", i)] = fmt.Sprintf("%s:%s", k, ck.RequestHeaders[k])
	}

	return m
}

// Params returns a map of parameters for an HttpCheck that can be sent along
// with an HTTP POST request. They are the same than the Put params, but
// empty strings cleared out, to avoid Pingdom API reject the request.
func (ck *HttpCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}
	params["type"] = "http"

	return params
}

// Determine whether the HttpCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *HttpCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}

	if ck.ShouldContain != "" && ck.ShouldNotContain != "" {
		return fmt.Errorf("`ShouldContain` and `ShouldNotContain` must not be declared at the same time")
	}

	return nil
}

// Params returns a map of parameters for a PingCheck that can be sent along
// with an HTTP PUT request
func (ck *PingCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	return m
}

// Params returns a map of parameters for a PingCheck that can be sent along
// with an HTTP POST request. Same as PUT.
func (ck *PingCheck) PostParams() map[string]string {
	params := ck.PutParams()
	params["type"] = "ping"
	return params
}

// Determine whether the PingCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *PingCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}
	return nil
}

// Params returns a map of parameters for a TCPCheck that can be sent along
// with an HTTP PUT request
func (ck *TCPCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
		"port":             strconv.Itoa(ck.Port),
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	if ck.StringToSend != "" {
		m["stringtosend"] = ck.StringToSend
	}

	if ck.StringToExpect != "" {
		m["stringtoexpect"] = ck.StringToExpect
	}

	return m
}

// Params returns a map of parameters for a TCPCheck that can be sent along
// with an HTTP POST request. Same as PUT.
func (ck *TCPCheck) PostParams() map[string]string {
	params := ck.PutParams()
	params["type"] = "tcp"
	return params
}

// Determine whether the TCPCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *TCPCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}

	if ck.Port < 1 {
		return fmt.Errorf("Invalid value for `Port`.  Must contain an integer >= 1")
	}

	return nil
}

func intListToCDString(integers []int) string {
	var CDString string
	for i, item := range integers {
		if i == 0 {
			CDString = strconv.Itoa(item)
		} else {
			CDString = fmt.Sprintf("%v,%d", CDString, item)
		}
	}
	return CDString
}
<commit_msg>Remove empty elements like in HTTPCheck before posting to prevent 500 response code<commit_after>package pingdom

import (
	"fmt"
	"sort"
	"strconv"
)

// HttpCheck represents a Pingdom http check.
type HttpCheck struct {
	Name                     string            `json:"name"`
	Hostname                 string            `json:"hostname,omitempty"`
	Resolution               int               `json:"resolution,omitempty"`
	Paused                   bool              `json:"paused,omitempty"`
	SendNotificationWhenDown int               `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int               `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool              `json:"notifywhenbackup,omitempty"`
	Url                      string            `json:"url,omitempty"`
	Encryption               bool              `json:"encryption,omitempty"`
	Port                     int               `json:"port,omitempty"`
	Username                 string            `json:"username,omitempty"`
	Password                 string            `json:"password,omitempty"`
	ShouldContain            string            `json:"shouldcontain,omitempty"`
	ShouldNotContain         string            `json:"shouldnotcontain,omitempty"`
	PostData                 string            `json:"postdata,omitempty"`
	RequestHeaders           map[string]string `json:"requestheaders,omitempty"`
	IntegrationIds           []int             `json:"integrationids,omitempty"`
	Tags                     string            `json:"tags,omitempty"`
	ProbeFilters             string            `json:"probe_filters,omitempty"`
	UserIds                  []int             `json:"userids,omitempty"`
	TeamIds                  []int             `json:"teamids,omitempty"`
}

// PingCheck represents a Pingdom ping check
type PingCheck struct {
	Name                     string `json:"name"`
	Hostname                 string `json:"hostname,omitempty"`
	Resolution               int    `json:"resolution,omitempty"`
	Paused                   bool   `json:"paused,omitempty"`
	SendNotificationWhenDown int    `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int    `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool   `json:"notifywhenbackup,omitempty"`
	IntegrationIds           []int  `json:"integrationids,omitempty"`
	ProbeFilters             string `json:"probe_filters,omitempty"`
	UserIds                  []int  `json:"userids,omitempty"`
	TeamIds                  []int  `json:"teamids,omitempty"`
}

// TCPCheck represents a Pingdom TCP check
type TCPCheck struct {
	Name                     string `json:"name"`
	Hostname                 string `json:"hostname,omitempty"`
	Resolution               int    `json:"resolution,omitempty"`
	Paused                   bool   `json:"paused,omitempty"`
	SendNotificationWhenDown int    `json:"sendnotificationwhendown,omitempty"`
	NotifyAgainEvery         int    `json:"notifyagainevery,omitempty"`
	NotifyWhenBackup         bool   `json:"notifywhenbackup,omitempty"`
	IntegrationIds           []int  `json:"integrationids,omitempty"`
	ProbeFilters             string `json:"probe_filters,omitempty"`
	UserIds                  []int  `json:"userids,omitempty"`
	TeamIds                  []int  `json:"teamids,omitempty"`
	Port                     int    `json:"port"`
	StringToSend             string `json:"stringtosend,omitempty"`
	StringToExpect           string `json:"stringtoexpect,omitempty"`
}

// Params returns a map of parameters for an HttpCheck that can be sent along
// with an HTTP PUT request
func (ck *HttpCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"url":              ck.Url,
		"encryption":       strconv.FormatBool(ck.Encryption),
		"postdata":         ck.PostData,
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"tags":             ck.Tags,
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
	}

	// Ignore zero values
	if ck.Port != 0 {
		m["port"] = strconv.Itoa(ck.Port)
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	// ShouldContain and ShouldNotContain are mutually exclusive.
	// But we must define one so they can be emptied if required.
	if ck.ShouldContain != "" {
		m["shouldcontain"] = ck.ShouldContain
	} else {
		m["shouldnotcontain"] = ck.ShouldNotContain
	}

	// Convert auth
	if ck.Username != "" {
		m["auth"] = fmt.Sprintf("%s:%s", ck.Username, ck.Password)
	}

	// Convert headers
	var headers []string
	for k := range ck.RequestHeaders {
		headers = append(headers, k)
	}
	sort.Strings(headers)
	for i, k := range headers {
		m[fmt.Sprintf("requestheader%d", i)] = fmt.Sprintf("%s:%s", k, ck.RequestHeaders[k])
	}

	return m
}

// Params returns a map of parameters for an HttpCheck that can be sent along
// with an HTTP POST request. They are the same than the Put params, but
// empty strings cleared out, to avoid Pingdom API reject the request.
func (ck *HttpCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}
	params["type"] = "http"

	return params
}

// Determine whether the HttpCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *HttpCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}

	if ck.ShouldContain != "" && ck.ShouldNotContain != "" {
		return fmt.Errorf("`ShouldContain` and `ShouldNotContain` must not be declared at the same time")
	}

	return nil
}

// Params returns a map of parameters for a PingCheck that can be sent along
// with an HTTP PUT request
func (ck *PingCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	return m
}

// Params returns a map of parameters for a PingCheck that can be sent along
// with an HTTP POST request. Same as PUT.
func (ck *PingCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}

	params["type"] = "ping"
	return params
}

// Determine whether the PingCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *PingCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}
	return nil
}

// Params returns a map of parameters for a TCPCheck that can be sent along
// with an HTTP PUT request
func (ck *TCPCheck) PutParams() map[string]string {
	m := map[string]string{
		"name":             ck.Name,
		"host":             ck.Hostname,
		"resolution":       strconv.Itoa(ck.Resolution),
		"paused":           strconv.FormatBool(ck.Paused),
		"notifyagainevery": strconv.Itoa(ck.NotifyAgainEvery),
		"notifywhenbackup": strconv.FormatBool(ck.NotifyWhenBackup),
		"integrationids":   intListToCDString(ck.IntegrationIds),
		"probe_filters":    ck.ProbeFilters,
		"userids":          intListToCDString(ck.UserIds),
		"teamids":          intListToCDString(ck.TeamIds),
		"port":             strconv.Itoa(ck.Port),
	}

	if ck.SendNotificationWhenDown != 0 {
		m["sendnotificationwhendown"] = strconv.Itoa(ck.SendNotificationWhenDown)
	}

	if ck.StringToSend != "" {
		m["stringtosend"] = ck.StringToSend
	}

	if ck.StringToExpect != "" {
		m["stringtoexpect"] = ck.StringToExpect
	}

	return m
}

// Params returns a map of parameters for a TCPCheck that can be sent along
// with an HTTP POST request. Same as PUT.
func (ck *TCPCheck) PostParams() map[string]string {
	params := ck.PutParams()

	for k, v := range params {
		if v == "" {
			delete(params, k)
		}
	}

	params["type"] = "tcp"
	return params
}

// Determine whether the TCPCheck contains valid fields.  This can be
// used to guard against sending illegal values to the Pingdom API
func (ck *TCPCheck) Valid() error {
	if ck.Name == "" {
		return fmt.Errorf("Invalid value for `Name`.  Must contain non-empty string")
	}

	if ck.Hostname == "" {
		return fmt.Errorf("Invalid value for `Hostname`.  Must contain non-empty string")
	}

	if ck.Resolution != 1 && ck.Resolution != 5 && ck.Resolution != 15 &&
		ck.Resolution != 30 && ck.Resolution != 60 {
		return fmt.Errorf("Invalid value %v for `Resolution`.  Allowed values are [1,5,15,30,60].", ck.Resolution)
	}

	if ck.Port < 1 {
		return fmt.Errorf("Invalid value for `Port`.  Must contain an integer >= 1")
	}

	return nil
}

func intListToCDString(integers []int) string {
	var CDString string
	for i, item := range integers {
		if i == 0 {
			CDString = strconv.Itoa(item)
		} else {
			CDString = fmt.Sprintf("%v,%d", CDString, item)
		}
	}
	return CDString
}
<|endoftext|>


<commit_before>// Config client. Also talks to coordinator for watches and versions.
//Typical use case is to get a dynamic bucket and use it to read configuration.
//The dynamic bucket is auto-updated.
//
//Sample usage:
//
// Create client instance with 50 as the size of LRU cache
//      client := cfgsvc.NewConfigServiceClient("http://localhost:8080", 50)
//
//
// get key and check its value
//  if flag := client.GetDynamicBucket("mybucket").GetBool("myflag"); flag {
//      do stuff
//  }
//
//
// If you do not wish to remember the bucket name in runtime, you can
// use the bucket struct directly, it will be auto-updated by client.
//  bucket := client.GetDynamicBucket("mybucket");
//
//
//  endpoint := bucket.GetString("endpoint");
package cfgsvc

import (
	"encoding/json"
	"errors"
	"github.com/hashicorp/golang-lru"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"
	"strings"
	"fmt"
)

// ConfigServiceClient provides API to interact with config service to
// read and watch for configuration changes
type ConfigServiceClient struct {
	httpClient         *HttpClient
	instanceMetadata   *InstanceMetadata
	dynamicBucketCache *lru.Cache
	staticBucketCache  *lru.Cache
	mutex              sync.Mutex
}

type InstanceMetadata struct {
	App           string `json:"app"`
	Zone          string `json:"zone"`
	InstanceGroup string `json:"instance_group"`
	Hostname      string `json:"hostname"`
	PrimaryIP     string `json:"primary_ip"`
	Id 			  string `json:"id"`
	Vpc 		  string `json:"vpc_name"`
	VpcSubnet     string `json:"vpc_subnet_name"`
}

type CfgSvcApiOverrides struct {
	Endpoint      string
}

const InstanceMetadataFile = "/etc/default/megh/instance_metadata.json"
const DefaultZone = "in-mumbai-preprod"
const CfgSvcApiOverridesFile = "/etc/default/cfg-api"
const CloudCliEndpoint = "http://10.47.255.6:8080"

var instVpcToCfgSvc = map[string]string{
	"fk-helios": "http://10.47.7.149",
	"fk-preprod": "http://10.85.42.8",
}

var instZoneToCfgsvc = map[string]string{
	"in-mumbai-prod":    "http://10.85.50.3",
	"in-mumbai-preprod":    "http://10.85.42.8",
	"in-mumbai-preprod-b":    "http://10.85.42.8",
	"in-mumbai-gateway": "http://10.85.50.3",
	"in-chennai-1":      "http://10.47.0.101",
}

// var skipListForVpcCheck = [...]string{"in-mumbai-preprod", "in-mumbai-preprod-b", "in-mumbai-prod", "in-mumbai-gateway", "#NULL#"}

const LATEST_VERSION = -1

// NewConfigServiceClient creates a new instance of config service client and returns its pointer.
func NewConfigServiceClient(cacheSize int) (*ConfigServiceClient, error) {

	client := &ConfigServiceClient{}

	// get instance metadata
	meta := readInstMetadata()

	netHttpClient := &http.Client{Timeout: time.Duration(60 * time.Second)}

	// get url
	url := ""
	ok := false

	overrides, err := getOverrides(CfgSvcApiOverridesFile)
	if err == nil && len(overrides.Endpoint) > 0 {
		log.Println("Overriding endpoint")
		url = overrides.Endpoint
	} else {
		log.Println("Attempting to get endpoint for vpc " + meta.Vpc)
		vpc := strings.ToLower(meta.Vpc)
		if url, ok = instVpcToCfgSvc[vpc]; !ok {
			log.Println("Attempting to get endpoint for zone " + meta.Zone)
			if url, ok = instZoneToCfgsvc[meta.Zone]; !ok {
				log.Println("Instance zone not found, defaulting to " + DefaultZone)
				url = instZoneToCfgsvc[DefaultZone]
			}
		}
	}
	log.Println("Using endpoint: " + url)

	// create client
	httpClient, err := NewHttpClient(netHttpClient, url, meta)
	if err != nil {
		return nil, err
	}

	// dynamic cache
	client.dynamicBucketCache, err = lru.NewWithEvict(cacheSize, func(bucketName interface{}, value interface{}) {
		dynamicBucket := value.(*DynamicBucket)
		log.Println("Removing bucket from local cache: ", bucketName)
		dynamicBucket.Disconnected(errors.New("Bucket evicted from cache, please fetch it again"))
		dynamicBucket.shutdown()
	})

	// static cache
	client.staticBucketCache, err = lru.NewWithEvict(cacheSize, func(bucketName interface{}, value interface{}) {
		log.Println("Removing bucket from local cache: ", bucketName)
	})

	if err != nil {
		return nil, err
	} else {
		client.httpClient = httpClient
		return client, nil
	}
}

//Get a dynamic bucket which is auto-updated by a setting watch.
//Keeps a local reference of the static bucket for updating and caching.
func (this *ConfigServiceClient) GetDynamicBucket(name string) (*DynamicBucket, error) {
	if val, ok := this.dynamicBucketCache.Get(name); ok {
		dynamicBucket := val.(*DynamicBucket)
		return dynamicBucket, nil
	} else {
		//Use mutex to ensure the bucket will be fetched only once!
		this.mutex.Lock()
		defer this.mutex.Unlock()

		//Check cache again to see if the another thread has
		//already initialized the bucket
		if val, ok := this.dynamicBucketCache.Get(name); ok {
			dynamicBucket := val.(*DynamicBucket)
			return dynamicBucket, nil
		} else {
			// Initialize the bucket if this the first time
			return this.initDynamicBucket(name)
		}
	}
}

//Initialises a dynamic bucket given the bucket name
func (this *ConfigServiceClient) initDynamicBucket(name string) (*DynamicBucket, error) {
	log.Println("Initializing Config bucket: " + name)

	dynamicBucket := &DynamicBucket{httpClient: this.httpClient}

	err := ValidateBucketName(name)
	if err != nil {
		return nil, err
	}

	err = dynamicBucket.init(name)

	if err != nil {
		log.Println("Error fetching bucket: ", err)
		return nil, err
	} else {
		this.dynamicBucketCache.Add(name, dynamicBucket)
		go this.httpClient.WatchBucket(name, this.dynamicBucketCache, dynamicBucket)
		return dynamicBucket, nil
	}
}

//Get a bucket with given version. It does not set any watches.
func (this *ConfigServiceClient) GetBucket(name string, version int) (*Bucket, error) {
	if val, ok := this.staticBucketCache.Get(cacheKey(name, version)); ok {
		bucket := val.(*Bucket)
		return bucket, nil
	} else {
		//Use mutex to ensure the bucket will be fetched only once!
		this.mutex.Lock()
		defer this.mutex.Unlock()

		//Check cache again to see if the another thread has
		//already initialized the bucket
		if val, ok := this.staticBucketCache.Get(cacheKey(name, version)); ok {
			bucket := val.(*Bucket)
			return bucket, nil
		} else {
			// Initialize the bucket if this the first time
			return this.initStaticBucket(name, version)
		}
	}
}

//Initialises a bucket with given version. It does not set any watches.
func (this *ConfigServiceClient) initStaticBucket(name string, version int) (*Bucket, error) {
	log.Println("Initializing Config bucket: " + name)

	err := ValidateBucketName(name)
	if err != nil {
		return nil, err
	}
	bucket, err := this.httpClient.GetBucket(name, version)
	if err != nil {
		log.Println("Error fetching bucket: ", err)
		return nil, err
	} else {
		this.staticBucketCache.Add(cacheKey(name, version), bucket)
		return bucket, nil
	}
}

func cacheKey(name string, version int) string {
	return name + ":" + strconv.Itoa(version)
}

// func skipVpcCheck(zone string) bool {
// 	for _, z := range skipListForVpcCheck {
//         if z == zone {
//             return true
//         }
//     }
//     return false
// }

func getProperties(fileName string) (map[string]string, error) {
	bytes, err := ioutil.ReadFile(fileName)

	if err != nil {
		log.Println("Failed to read file: " + fileName + ". Ignoring overrides")
		return nil, err
	}

	lines := strings.Split(string(bytes[:]), "\n")

	properties := map[string]string{}
	for _, line := range lines {
		if len(line) > 0 {
			kv := strings.Split(line, "=")
			if len(kv) != 2 {
				return nil, fmt.Errorf("format error in line : \"%s\"", line)
			}

			key := strings.TrimSpace(kv[0])
			value := strings.TrimSpace(kv[1])

			if len(key) == 0 || len(value) == 0 {
				return nil, fmt.Errorf("format error in line : \"%s\"", line)
			}

			properties[key] = value
		}
	}

	return properties, nil
}

func getOverrides(fileName string) (CfgSvcApiOverrides, error) {
	overrides := CfgSvcApiOverrides{Endpoint : ""}

	properties, err := getProperties(fileName)

	if err != nil {
		return overrides, err
	}

	host, ok := properties["host"]
	if !ok {
		return overrides, fmt.Errorf("empty overrides")
	}

	port_str, ok := properties["port"]

	if !ok {
		port_str = "80"
	} else {
		_, err = strconv.Atoi(port_str)
		if err != nil {
			return overrides, fmt.Errorf("port is not a number")
		}
	}

	overrides.Endpoint = "http://" + host + ":" + port_str

	return overrides, nil
}

// func doRequest(httpClient *http.Client, url string) ([]byte, error) {
// 	req, _ := http.NewRequest("GET", url, nil)
// 	req.Header.Add("Accept", "application/json")

// 	resp, err := httpClient.Do(req)

// 	if err != nil {
// 		log.Println("Failed to do request. error: " + err.Error())
// 		return nil, err
// 	}

// 	defer resp.Body.Close()

// 	return ioutil.ReadAll(resp.Body)
// }

// func getVpcSubnetName(httpClient *http.Client, meta *InstanceMetadata) (string, error) {

// 	url := CloudCliEndpoint + "/compute/v2/apps/" + meta.App + "/zones/" + meta.Zone + "/instances/" + meta.Id

// 	resp_body, err := doRequest(httpClient, url)

// 	if err != nil {
// 		return "", err
// 	}

// 	var jsonVal map[string]interface{}

// 	if err := json.Unmarshal(resp_body, &jsonVal); err != nil {
//         log.Println("Error parsing cloud cli rsponse as json. error: " + err.Error())
//         return "", err
//     }

//     vpcname := jsonVal["vpc_subnet_name"]
//     if vpcname != nil {
// 		return strings.ToLower(vpcname.(string)), nil
//     }

//     return "", fmt.Errorf("vpc name not found")
// }

func readInstMetadata() *InstanceMetadata {

	// create instance
	meta := &InstanceMetadata{}

	// read from json
	jsn, err := os.Open(InstanceMetadataFile)
	if err != nil {
		log.Println("Error opening " + InstanceMetadataFile + ": " + err.Error())
	}

	// parse json
	jsonParser := json.NewDecoder(jsn)
	if err = jsonParser.Decode(&meta); err != nil {
		log.Println("Error parsing instance metadata: " + err.Error())
	}

	// get hostname
	if meta.Hostname, err = os.Hostname(); err != nil {
		log.Println("Error getting hostname, using from metadata (" + meta.Hostname + "): " + err.Error())
	}

	// get ipv4
	if meta.PrimaryIP == "" {
		interfaces, _ := net.Interfaces()
		for _, inter := range interfaces {
			if addrs, err := inter.Addrs(); err == nil {
				for _, addr := range addrs {
					switch ip := addr.(type) {
					case *net.IPNet:
						if ip.IP.DefaultMask() != nil && !ip.IP.IsLoopback() {
							meta.PrimaryIP = ip.IP.To4().String()
						}
					}
				}
			}
		}
	}

	// defaults
	if meta.Zone == "" {
		meta.InstanceGroup = "#NULL#"
	}
	if meta.App == "" {
		meta.App = "#NULL#"
	}
	if meta.InstanceGroup == "" {
		meta.InstanceGroup = "#NULL#"
	}
	if meta.Vpc == "" {
		meta.Vpc = "#NULL#"
	}
	if meta.VpcSubnet == "" {
		meta.VpcSubnet = "#NULL#"
	}
	return meta
}
<commit_msg>endpoint discovery for hyderabad zone<commit_after>// Config client. Also talks to coordinator for watches and versions.
//Typical use case is to get a dynamic bucket and use it to read configuration.
//The dynamic bucket is auto-updated.
//
//Sample usage:
//
// Create client instance with 50 as the size of LRU cache
//      client := cfgsvc.NewConfigServiceClient("http://localhost:8080", 50)
//
//
// get key and check its value
//  if flag := client.GetDynamicBucket("mybucket").GetBool("myflag"); flag {
//      do stuff
//  }
//
//
// If you do not wish to remember the bucket name in runtime, you can
// use the bucket struct directly, it will be auto-updated by client.
//  bucket := client.GetDynamicBucket("mybucket");
//
//
//  endpoint := bucket.GetString("endpoint");
package cfgsvc

import (
	"encoding/json"
	"errors"
	"github.com/hashicorp/golang-lru"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"strconv"
	"sync"
	"time"
	"strings"
	"fmt"
)

// ConfigServiceClient provides API to interact with config service to
// read and watch for configuration changes
type ConfigServiceClient struct {
	httpClient         *HttpClient
	instanceMetadata   *InstanceMetadata
	dynamicBucketCache *lru.Cache
	staticBucketCache  *lru.Cache
	mutex              sync.Mutex
}

type InstanceMetadata struct {
	App           string `json:"app"`
	Zone          string `json:"zone"`
	InstanceGroup string `json:"instance_group"`
	Hostname      string `json:"hostname"`
	PrimaryIP     string `json:"primary_ip"`
	Id 			  string `json:"id"`
	Vpc 		  string `json:"vpc_name"`
	VpcSubnet     string `json:"vpc_subnet_name"`
}

type CfgSvcApiOverrides struct {
	Endpoint      string
}

const InstanceMetadataFile = "/etc/default/megh/instance_metadata.json"
const DefaultZone = "in-mumbai-preprod"
const CfgSvcApiOverridesFile = "/etc/default/cfg-api"
const CloudCliEndpoint = "http://10.47.255.6:8080"

var instVpcToCfgSvc = map[string]string{
	"fk-helios": "http://10.47.7.149",
	"fk-preprod": "http://10.85.42.8",
}

var instZoneToCfgsvc = map[string]string{
	"in-mumbai-prod":    "http://10.85.50.3",
	"in-mumbai-preprod":    "http://10.85.42.8",
	"in-mumbai-preprod-b":    "http://10.85.42.8",
	"in-mumbai-gateway": "http://10.85.50.3",
	"in-chennai-1":      "http://10.47.0.101",
	"in-hyderabad-1": "http://10.24.0.2",
}

// var skipListForVpcCheck = [...]string{"in-mumbai-preprod", "in-mumbai-preprod-b", "in-mumbai-prod", "in-mumbai-gateway", "#NULL#"}

const LATEST_VERSION = -1

// NewConfigServiceClient creates a new instance of config service client and returns its pointer.
func NewConfigServiceClient(cacheSize int) (*ConfigServiceClient, error) {

	client := &ConfigServiceClient{}

	// get instance metadata
	meta := readInstMetadata()

	netHttpClient := &http.Client{Timeout: time.Duration(60 * time.Second)}

	// get url
	url := ""
	ok := false

	overrides, err := getOverrides(CfgSvcApiOverridesFile)
	if err == nil && len(overrides.Endpoint) > 0 {
		log.Println("Overriding endpoint")
		url = overrides.Endpoint
	} else {
		log.Println("Attempting to get endpoint for vpc " + meta.Vpc)
		vpc := strings.ToLower(meta.Vpc)
		if url, ok = instVpcToCfgSvc[vpc]; !ok {
			log.Println("Attempting to get endpoint for zone " + meta.Zone)
			if url, ok = instZoneToCfgsvc[meta.Zone]; !ok {
				log.Println("Instance zone not found, defaulting to " + DefaultZone)
				url = instZoneToCfgsvc[DefaultZone]
			}
		}
	}
	log.Println("Using endpoint: " + url)

	// create client
	httpClient, err := NewHttpClient(netHttpClient, url, meta)
	if err != nil {
		return nil, err
	}

	// dynamic cache
	client.dynamicBucketCache, err = lru.NewWithEvict(cacheSize, func(bucketName interface{}, value interface{}) {
		dynamicBucket := value.(*DynamicBucket)
		log.Println("Removing bucket from local cache: ", bucketName)
		dynamicBucket.Disconnected(errors.New("Bucket evicted from cache, please fetch it again"))
		dynamicBucket.shutdown()
	})

	// static cache
	client.staticBucketCache, err = lru.NewWithEvict(cacheSize, func(bucketName interface{}, value interface{}) {
		log.Println("Removing bucket from local cache: ", bucketName)
	})

	if err != nil {
		return nil, err
	} else {
		client.httpClient = httpClient
		return client, nil
	}
}

//Get a dynamic bucket which is auto-updated by a setting watch.
//Keeps a local reference of the static bucket for updating and caching.
func (this *ConfigServiceClient) GetDynamicBucket(name string) (*DynamicBucket, error) {
	if val, ok := this.dynamicBucketCache.Get(name); ok {
		dynamicBucket := val.(*DynamicBucket)
		return dynamicBucket, nil
	} else {
		//Use mutex to ensure the bucket will be fetched only once!
		this.mutex.Lock()
		defer this.mutex.Unlock()

		//Check cache again to see if the another thread has
		//already initialized the bucket
		if val, ok := this.dynamicBucketCache.Get(name); ok {
			dynamicBucket := val.(*DynamicBucket)
			return dynamicBucket, nil
		} else {
			// Initialize the bucket if this the first time
			return this.initDynamicBucket(name)
		}
	}
}

//Initialises a dynamic bucket given the bucket name
func (this *ConfigServiceClient) initDynamicBucket(name string) (*DynamicBucket, error) {
	log.Println("Initializing Config bucket: " + name)

	dynamicBucket := &DynamicBucket{httpClient: this.httpClient}

	err := ValidateBucketName(name)
	if err != nil {
		return nil, err
	}

	err = dynamicBucket.init(name)

	if err != nil {
		log.Println("Error fetching bucket: ", err)
		return nil, err
	} else {
		this.dynamicBucketCache.Add(name, dynamicBucket)
		go this.httpClient.WatchBucket(name, this.dynamicBucketCache, dynamicBucket)
		return dynamicBucket, nil
	}
}

//Get a bucket with given version. It does not set any watches.
func (this *ConfigServiceClient) GetBucket(name string, version int) (*Bucket, error) {
	if val, ok := this.staticBucketCache.Get(cacheKey(name, version)); ok {
		bucket := val.(*Bucket)
		return bucket, nil
	} else {
		//Use mutex to ensure the bucket will be fetched only once!
		this.mutex.Lock()
		defer this.mutex.Unlock()

		//Check cache again to see if the another thread has
		//already initialized the bucket
		if val, ok := this.staticBucketCache.Get(cacheKey(name, version)); ok {
			bucket := val.(*Bucket)
			return bucket, nil
		} else {
			// Initialize the bucket if this the first time
			return this.initStaticBucket(name, version)
		}
	}
}

//Initialises a bucket with given version. It does not set any watches.
func (this *ConfigServiceClient) initStaticBucket(name string, version int) (*Bucket, error) {
	log.Println("Initializing Config bucket: " + name)

	err := ValidateBucketName(name)
	if err != nil {
		return nil, err
	}
	bucket, err := this.httpClient.GetBucket(name, version)
	if err != nil {
		log.Println("Error fetching bucket: ", err)
		return nil, err
	} else {
		this.staticBucketCache.Add(cacheKey(name, version), bucket)
		return bucket, nil
	}
}

func cacheKey(name string, version int) string {
	return name + ":" + strconv.Itoa(version)
}

// func skipVpcCheck(zone string) bool {
// 	for _, z := range skipListForVpcCheck {
//         if z == zone {
//             return true
//         }
//     }
//     return false
// }

func getProperties(fileName string) (map[string]string, error) {
	bytes, err := ioutil.ReadFile(fileName)

	if err != nil {
		log.Println("Failed to read file: " + fileName + ". Ignoring overrides")
		return nil, err
	}

	lines := strings.Split(string(bytes[:]), "\n")

	properties := map[string]string{}
	for _, line := range lines {
		if len(line) > 0 {
			kv := strings.Split(line, "=")
			if len(kv) != 2 {
				return nil, fmt.Errorf("format error in line : \"%s\"", line)
			}

			key := strings.TrimSpace(kv[0])
			value := strings.TrimSpace(kv[1])

			if len(key) == 0 || len(value) == 0 {
				return nil, fmt.Errorf("format error in line : \"%s\"", line)
			}

			properties[key] = value
		}
	}

	return properties, nil
}

func getOverrides(fileName string) (CfgSvcApiOverrides, error) {
	overrides := CfgSvcApiOverrides{Endpoint : ""}

	properties, err := getProperties(fileName)

	if err != nil {
		return overrides, err
	}

	host, ok := properties["host"]
	if !ok {
		return overrides, fmt.Errorf("empty overrides")
	}

	port_str, ok := properties["port"]

	if !ok {
		port_str = "80"
	} else {
		_, err = strconv.Atoi(port_str)
		if err != nil {
			return overrides, fmt.Errorf("port is not a number")
		}
	}

	overrides.Endpoint = "http://" + host + ":" + port_str

	return overrides, nil
}

// func doRequest(httpClient *http.Client, url string) ([]byte, error) {
// 	req, _ := http.NewRequest("GET", url, nil)
// 	req.Header.Add("Accept", "application/json")

// 	resp, err := httpClient.Do(req)

// 	if err != nil {
// 		log.Println("Failed to do request. error: " + err.Error())
// 		return nil, err
// 	}

// 	defer resp.Body.Close()

// 	return ioutil.ReadAll(resp.Body)
// }

// func getVpcSubnetName(httpClient *http.Client, meta *InstanceMetadata) (string, error) {

// 	url := CloudCliEndpoint + "/compute/v2/apps/" + meta.App + "/zones/" + meta.Zone + "/instances/" + meta.Id

// 	resp_body, err := doRequest(httpClient, url)

// 	if err != nil {
// 		return "", err
// 	}

// 	var jsonVal map[string]interface{}

// 	if err := json.Unmarshal(resp_body, &jsonVal); err != nil {
//         log.Println("Error parsing cloud cli rsponse as json. error: " + err.Error())
//         return "", err
//     }

//     vpcname := jsonVal["vpc_subnet_name"]
//     if vpcname != nil {
// 		return strings.ToLower(vpcname.(string)), nil
//     }

//     return "", fmt.Errorf("vpc name not found")
// }

func readInstMetadata() *InstanceMetadata {

	// create instance
	meta := &InstanceMetadata{}

	// read from json
	jsn, err := os.Open(InstanceMetadataFile)
	if err != nil {
		log.Println("Error opening " + InstanceMetadataFile + ": " + err.Error())
	}

	// parse json
	jsonParser := json.NewDecoder(jsn)
	if err = jsonParser.Decode(&meta); err != nil {
		log.Println("Error parsing instance metadata: " + err.Error())
	}

	// get hostname
	if meta.Hostname, err = os.Hostname(); err != nil {
		log.Println("Error getting hostname, using from metadata (" + meta.Hostname + "): " + err.Error())
	}

	// get ipv4
	if meta.PrimaryIP == "" {
		interfaces, _ := net.Interfaces()
		for _, inter := range interfaces {
			if addrs, err := inter.Addrs(); err == nil {
				for _, addr := range addrs {
					switch ip := addr.(type) {
					case *net.IPNet:
						if ip.IP.DefaultMask() != nil && !ip.IP.IsLoopback() {
							meta.PrimaryIP = ip.IP.To4().String()
						}
					}
				}
			}
		}
	}

	// defaults
	if meta.Zone == "" {
		meta.InstanceGroup = "#NULL#"
	}
	if meta.App == "" {
		meta.App = "#NULL#"
	}
	if meta.InstanceGroup == "" {
		meta.InstanceGroup = "#NULL#"
	}
	if meta.Vpc == "" {
		meta.Vpc = "#NULL#"
	}
	if meta.VpcSubnet == "" {
		meta.VpcSubnet = "#NULL#"
	}
	return meta
}
<|endoftext|>


<commit_before>/*
Copyright 2014 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 api

import (
	"strings"
)

// This file contains API types that are unversioned.

// APIVersions lists the api versions that are available, to allow
// version negotiation. APIVersions isn't just an unnamed array of
// strings in order to allow for future evolution, though unversioned
type APIVersions struct {
	Versions []string `json:"versions"`
}

// RootPaths lists the paths available at root.
// For example: "/healthz", "/api".
type RootPaths struct {
	Paths []string `json:"paths"`
}

// preV1Beta3 returns true if the provided API version is an API introduced before v1beta3.
func PreV1Beta3(version string) bool {
	return version == "v1beta1" || version == "v1beta2"
}

// TODO: remove me when watch is refactored
func LabelSelectorQueryParam(version string) string {
	if PreV1Beta3(version) {
		return "labels"
	}
	return "labelSelector"
}

// TODO: remove me when watch is refactored
func FieldSelectorQueryParam(version string) string {
	if PreV1Beta3(version) {
		return "fields"
	}
	return "fieldSelector"
}

// String returns available api versions as a human-friendly version string.
func (apiVersions APIVersions) String() string {
	return strings.Join(apiVersions.Versions, ",")
}

func (apiVersions APIVersions) GoString() string {
	return apiVersions.String()
}
<commit_msg>Swagger output for PATCH can be more strongly typed<commit_after>/*
Copyright 2014 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 api

import (
	"strings"
)

// This file contains API types that are unversioned.

// APIVersions lists the api versions that are available, to allow
// version negotiation. APIVersions isn't just an unnamed array of
// strings in order to allow for future evolution, though unversioned
type APIVersions struct {
	Versions []string `json:"versions"`
}

// RootPaths lists the paths available at root.
// For example: "/healthz", "/api".
type RootPaths struct {
	Paths []string `json:"paths"`
}

// preV1Beta3 returns true if the provided API version is an API introduced before v1beta3.
func PreV1Beta3(version string) bool {
	return version == "v1beta1" || version == "v1beta2"
}

// TODO: remove me when watch is refactored
func LabelSelectorQueryParam(version string) string {
	if PreV1Beta3(version) {
		return "labels"
	}
	return "labelSelector"
}

// TODO: remove me when watch is refactored
func FieldSelectorQueryParam(version string) string {
	if PreV1Beta3(version) {
		return "fields"
	}
	return "fieldSelector"
}

// String returns available api versions as a human-friendly version string.
func (apiVersions APIVersions) String() string {
	return strings.Join(apiVersions.Versions, ",")
}

func (apiVersions APIVersions) GoString() string {
	return apiVersions.String()
}

// Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.
type Patch struct{}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package kops

import (
	"fmt"
	"strings"

	"k8s.io/kops/upup/pkg/fi/utils"
)

// ParseInstanceGroupRole converts a string to an InstanceGroupRole.
//
// If lenient is set to true, the function will match pluralised words too.
// It will return the instance group role and true if a match was found.
func ParseInstanceGroupRole(input string, lenient bool) (InstanceGroupRole, bool) {
	findRole := strings.ToLower(input)
	if lenient {
		// Accept pluralized "bastions" for "bastion"
		findRole = strings.TrimSuffix(findRole, "s")
	}
	findRole = strings.Replace(findRole, "controlplane", "control-plane", 1)

	for _, role := range AllInstanceGroupRoles {
		s := role.ToLowerString()
		if lenient {
			s = strings.TrimSuffix(s, "s")
		}
		if s == findRole {
			return role, true
		}
	}

	if lenient && strings.ToLower(findRole) == "master" {
		return InstanceGroupRoleControlPlane, true
	}

	return "", false
}

// ParseRawYaml parses an object just using yaml, without the full api machinery
// Deprecated: prefer using the API machinery
func ParseRawYaml(data []byte, dest interface{}) error {
	// Yaml can't parse empty strings
	configString := string(data)
	configString = strings.TrimSpace(configString)

	if configString != "" {
		err := utils.YamlUnmarshal([]byte(configString), dest)
		if err != nil {
			return fmt.Errorf("error parsing configuration: %v", err)
		}
	}

	return nil
}

// ToRawYaml marshals an object to yaml, without the full api machinery
// Deprecated: prefer using the API machinery
func ToRawYaml(obj interface{}) ([]byte, error) {
	data, err := utils.YamlMarshal(obj)
	if err != nil {
		return nil, fmt.Errorf("error converting to yaml: %v", err)
	}

	return data, nil
}
<commit_msg>Disallow unknown fields in create cluster integration tests<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package kops

import (
	"fmt"
	"strings"

	"k8s.io/kops/upup/pkg/fi/utils"
	"sigs.k8s.io/yaml"
)

// ParseInstanceGroupRole converts a string to an InstanceGroupRole.
//
// If lenient is set to true, the function will match pluralised words too.
// It will return the instance group role and true if a match was found.
func ParseInstanceGroupRole(input string, lenient bool) (InstanceGroupRole, bool) {
	findRole := strings.ToLower(input)
	if lenient {
		// Accept pluralized "bastions" for "bastion"
		findRole = strings.TrimSuffix(findRole, "s")
	}
	findRole = strings.Replace(findRole, "controlplane", "control-plane", 1)

	for _, role := range AllInstanceGroupRoles {
		s := role.ToLowerString()
		if lenient {
			s = strings.TrimSuffix(s, "s")
		}
		if s == findRole {
			return role, true
		}
	}

	if lenient && strings.ToLower(findRole) == "master" {
		return InstanceGroupRoleControlPlane, true
	}

	return "", false
}

// ParseRawYaml parses an object just using yaml, without the full api machinery
// Deprecated: prefer using the API machinery
func ParseRawYaml(data []byte, dest interface{}) error {
	// Yaml can't parse empty strings
	configString := string(data)
	configString = strings.TrimSpace(configString)

	if configString != "" {
		err := yaml.Unmarshal([]byte(configString), dest, yaml.DisallowUnknownFields)
		if err != nil {
			return fmt.Errorf("error parsing configuration: %v", err)
		}
	}

	return nil
}

// ToRawYaml marshals an object to yaml, without the full api machinery
// Deprecated: prefer using the API machinery
func ToRawYaml(obj interface{}) ([]byte, error) {
	data, err := utils.YamlMarshal(obj)
	if err != nil {
		return nil, fmt.Errorf("error converting to yaml: %v", err)
	}

	return data, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 Google Inc. 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 apiserver

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

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
	watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"

	"github.com/golang/glog"
	"golang.org/x/net/websocket"
)

type WatchHandler struct {
	storage         map[string]RESTStorage
	codec           runtime.Codec
	canonicalPrefix string
	selfLinker      runtime.SelfLinker
}

// setSelfLinkAddName sets the self link, appending the object's name to the canonical path & type.
func (h *WatchHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request) error {
	name, err := h.selfLinker.Name(obj)
	if err != nil {
		return err
	}
	newURL := *req.URL
	newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, name)
	newURL.RawQuery = ""
	newURL.Fragment = ""
	return h.selfLinker.SetSelfLink(obj, newURL.String())
}

func getWatchParams(query url.Values) (label, field labels.Selector, resourceVersion string) {
	if s, err := labels.ParseSelector(query.Get("labels")); err != nil {
		label = labels.Everything()
	} else {
		label = s
	}
	if s, err := labels.ParseSelector(query.Get("fields")); err != nil {
		field = labels.Everything()
	} else {
		field = s
	}
	resourceVersion = query.Get("resourceVersion")
	return
}

var connectionUpgradeRegex = regexp.MustCompile("(^|.*,\\s*)upgrade($|\\s*,)")

func isWebsocketRequest(req *http.Request) bool {
	return connectionUpgradeRegex.MatchString(strings.ToLower(req.Header.Get("Connection"))) && strings.ToLower(req.Header.Get("Upgrade")) == "websocket"
}

// ServeHTTP processes watch requests.
func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" {
		notFound(w, req)
		return
	}

	namespace, kind, _, err := KindAndNamespace(req)
	if err != nil {
		notFound(w, req)
		return
	}
	ctx := api.WithNamespace(api.NewContext(), namespace)

	storage := h.storage[kind]
	if storage == nil {
		notFound(w, req)
		return
	}
	if watcher, ok := storage.(ResourceWatcher); ok {
		label, field, resourceVersion := getWatchParams(req.URL.Query())
		watching, err := watcher.Watch(ctx, label, field, resourceVersion)
		if err != nil {
			errorJSON(err, h.codec, w)
			return
		}

		// TODO: This is one watch per connection. We want to multiplex, so that
		// multiple watches of the same thing don't create two watches downstream.
		watchServer := &WatchServer{watching, h.codec, func(obj runtime.Object) {
			if err := h.setSelfLinkAddName(obj, req); err != nil {
				glog.Errorf("Failed to set self link for object %#v", obj)
			}
		}}
		if isWebsocketRequest(req) {
			websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req)
		} else {
			watchServer.ServeHTTP(w, req)
		}
		return
	}

	notFound(w, req)
}

// WatchServer serves a watch.Interface over a websocket or vanilla HTTP.
type WatchServer struct {
	watching watch.Interface
	codec    runtime.Codec
	fixup    func(runtime.Object)
}

// HandleWS implements a websocket handler.
func (w *WatchServer) HandleWS(ws *websocket.Conn) {
	done := make(chan struct{})
	go func() {
		var unused interface{}
		// Expect this to block until the connection is closed. Client should not
		// send anything.
		websocket.JSON.Receive(ws, &unused)
		close(done)
	}()
	for {
		select {
		case <-done:
			w.watching.Stop()
			return
		case event, ok := <-w.watching.ResultChan():
			if !ok {
				// End of results.
				return
			}
			w.fixup(event.Object)
			obj, err := watchjson.Object(w.codec, &event)
			if err != nil {
				// Client disconnect.
				w.watching.Stop()
				return
			}
			if err := websocket.JSON.Send(ws, obj); err != nil {
				// Client disconnect.
				w.watching.Stop()
				return
			}
		}
	}
}

// ServeHTTP serves a series of JSON encoded events via straight HTTP with
// Transfer-Encoding: chunked.
func (self *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	loggedW := httplog.LogOf(req, w)
	w = httplog.Unlogged(w)

	cn, ok := w.(http.CloseNotifier)
	if !ok {
		loggedW.Addf("unable to get CloseNotifier")
		http.NotFound(w, req)
		return
	}
	flusher, ok := w.(http.Flusher)
	if !ok {
		loggedW.Addf("unable to get Flusher")
		http.NotFound(w, req)
		return
	}

	w.Header().Set("Transfer-Encoding", "chunked")
	w.WriteHeader(http.StatusOK)
	flusher.Flush()

	encoder := watchjson.NewEncoder(w, self.codec)
	for {
		select {
		case <-cn.CloseNotify():
			self.watching.Stop()
			return
		case event, ok := <-self.watching.ResultChan():
			if !ok {
				// End of results.
				return
			}
			self.fixup(event.Object)
			if err := encoder.Encode(&event); err != nil {
				// Client disconnect.
				self.watching.Stop()
				return
			}
			flusher.Flush()
		}
	}
}
<commit_msg>Watching on invalid label/field selectors should error<commit_after>/*
Copyright 2014 Google Inc. 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 apiserver

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

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
	watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"

	"github.com/golang/glog"
	"golang.org/x/net/websocket"
)

type WatchHandler struct {
	storage         map[string]RESTStorage
	codec           runtime.Codec
	canonicalPrefix string
	selfLinker      runtime.SelfLinker
}

// setSelfLinkAddName sets the self link, appending the object's name to the canonical path & type.
func (h *WatchHandler) setSelfLinkAddName(obj runtime.Object, req *http.Request) error {
	name, err := h.selfLinker.Name(obj)
	if err != nil {
		return err
	}
	newURL := *req.URL
	newURL.Path = path.Join(h.canonicalPrefix, req.URL.Path, name)
	newURL.RawQuery = ""
	newURL.Fragment = ""
	return h.selfLinker.SetSelfLink(obj, newURL.String())
}

func getWatchParams(query url.Values) (label, field labels.Selector, resourceVersion string, err error) {
	s, perr := labels.ParseSelector(query.Get("labels"))
	if perr != nil {
		err = perr
		return
	}
	label = s

	s, perr = labels.ParseSelector(query.Get("fields"))
	if perr != nil {
		err = perr
		return
	}
	field = s

	resourceVersion = query.Get("resourceVersion")
	return
}

var connectionUpgradeRegex = regexp.MustCompile("(^|.*,\\s*)upgrade($|\\s*,)")

func isWebsocketRequest(req *http.Request) bool {
	return connectionUpgradeRegex.MatchString(strings.ToLower(req.Header.Get("Connection"))) && strings.ToLower(req.Header.Get("Upgrade")) == "websocket"
}

// ServeHTTP processes watch requests.
func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if req.Method != "GET" {
		notFound(w, req)
		return
	}

	namespace, kind, _, err := KindAndNamespace(req)
	if err != nil {
		notFound(w, req)
		return
	}
	ctx := api.WithNamespace(api.NewContext(), namespace)

	storage := h.storage[kind]
	if storage == nil {
		notFound(w, req)
		return
	}
	if watcher, ok := storage.(ResourceWatcher); ok {
		label, field, resourceVersion, err := getWatchParams(req.URL.Query())
		if err != nil {
			errorJSON(err, h.codec, w)
			return
		}
		watching, err := watcher.Watch(ctx, label, field, resourceVersion)
		if err != nil {
			errorJSON(err, h.codec, w)
			return
		}

		// TODO: This is one watch per connection. We want to multiplex, so that
		// multiple watches of the same thing don't create two watches downstream.
		watchServer := &WatchServer{watching, h.codec, func(obj runtime.Object) {
			if err := h.setSelfLinkAddName(obj, req); err != nil {
				glog.Errorf("Failed to set self link for object %#v", obj)
			}
		}}
		if isWebsocketRequest(req) {
			websocket.Handler(watchServer.HandleWS).ServeHTTP(httplog.Unlogged(w), req)
		} else {
			watchServer.ServeHTTP(w, req)
		}
		return
	}

	notFound(w, req)
}

// WatchServer serves a watch.Interface over a websocket or vanilla HTTP.
type WatchServer struct {
	watching watch.Interface
	codec    runtime.Codec
	fixup    func(runtime.Object)
}

// HandleWS implements a websocket handler.
func (w *WatchServer) HandleWS(ws *websocket.Conn) {
	done := make(chan struct{})
	go func() {
		var unused interface{}
		// Expect this to block until the connection is closed. Client should not
		// send anything.
		websocket.JSON.Receive(ws, &unused)
		close(done)
	}()
	for {
		select {
		case <-done:
			w.watching.Stop()
			return
		case event, ok := <-w.watching.ResultChan():
			if !ok {
				// End of results.
				return
			}
			w.fixup(event.Object)
			obj, err := watchjson.Object(w.codec, &event)
			if err != nil {
				// Client disconnect.
				w.watching.Stop()
				return
			}
			if err := websocket.JSON.Send(ws, obj); err != nil {
				// Client disconnect.
				w.watching.Stop()
				return
			}
		}
	}
}

// ServeHTTP serves a series of JSON encoded events via straight HTTP with
// Transfer-Encoding: chunked.
func (self *WatchServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	loggedW := httplog.LogOf(req, w)
	w = httplog.Unlogged(w)

	cn, ok := w.(http.CloseNotifier)
	if !ok {
		loggedW.Addf("unable to get CloseNotifier")
		http.NotFound(w, req)
		return
	}
	flusher, ok := w.(http.Flusher)
	if !ok {
		loggedW.Addf("unable to get Flusher")
		http.NotFound(w, req)
		return
	}

	w.Header().Set("Transfer-Encoding", "chunked")
	w.WriteHeader(http.StatusOK)
	flusher.Flush()

	encoder := watchjson.NewEncoder(w, self.codec)
	for {
		select {
		case <-cn.CloseNotify():
			self.watching.Stop()
			return
		case event, ok := <-self.watching.ResultChan():
			if !ok {
				// End of results.
				return
			}
			self.fixup(event.Object)
			if err := encoder.Encode(&event); err != nil {
				// Client disconnect.
				self.watching.Stop()
				return
			}
			flusher.Flush()
		}
	}
}
<|endoftext|>


<commit_before>package bootkube

import (
	"context"
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/golang/glog"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/clientcmd"
)

const (
	doesNotExist = "DoesNotExist"
)

func WaitUntilPodsRunning(c clientcmd.ClientConfig, pods []string, timeout time.Duration) error {
	sc, err := NewStatusController(c, pods)
	if err != nil {
		return err
	}
	sc.Run()

	if err := wait.Poll(5*time.Second, timeout, sc.AllRunning); err != nil {
		return fmt.Errorf("error while checking pod status: %v", err)
	}

	UserOutput("All self-hosted control plane components successfully started\n")
	return nil
}

type statusController struct {
	client        kubernetes.Interface
	podStore      cache.Store
	watchPods     []string
	lastPodPhases map[string]corev1.PodPhase
}

func NewStatusController(c clientcmd.ClientConfig, pods []string) (*statusController, error) {
	config, err := c.ClientConfig()
	if err != nil {
		return nil, err
	}
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	return &statusController{client: client, watchPods: pods}, nil
}

func (s *statusController) Run() {
	// TODO(yifan): Be more explicit about the labels so that we don't just
	// reply on the prefix of the pod name when looking for the pods we are interested.
	// E.g. For a scheduler pod, we will look for pods that has label `tier=control-plane`
	// and `component=kube-scheduler`.
	options := metav1.ListOptions{}
	podStore, podController := cache.NewInformer(
		&cache.ListWatch{
			ListFunc: func(lo metav1.ListOptions) (runtime.Object, error) {
				return s.client.CoreV1().Pods("").List(context.TODO(), options)
			},
			WatchFunc: func(lo metav1.ListOptions) (watch.Interface, error) {
				return s.client.CoreV1().Pods("").Watch(context.TODO(), options)
			},
		},
		&corev1.Pod{},
		30*time.Minute,
		cache.ResourceEventHandlerFuncs{},
	)
	s.podStore = podStore
	go podController.Run(wait.NeverStop)
}

func (s *statusController) AllRunning() (bool, error) {
	ps, err := s.PodStatus()
	if err != nil {
		glog.Infof("Error retriving pod statuses: %v", err)
		return false, nil
	}

	if s.lastPodPhases == nil {
		s.lastPodPhases = ps
	}

	// use lastPodPhases to print only pods whose phase has changed
	changed := !reflect.DeepEqual(ps, s.lastPodPhases)
	s.lastPodPhases = ps

	running := true
	for p, s := range ps {
		if changed {
			UserOutput("\tPod Status:%24s\t%s\n", p, s)
		}
		if s != corev1.PodRunning {
			running = false
		}
	}
	return running, nil
}

func (s *statusController) PodStatus() (map[string]corev1.PodPhase, error) {
	status := make(map[string]corev1.PodPhase)

	podNames := s.podStore.ListKeys()
	for _, watchedPod := range s.watchPods {
		// Pod names are suffixed with random data. Match on prefix
		for _, pn := range podNames {
			if strings.HasPrefix(pn, watchedPod) {
				watchedPod = pn
				break
			}
		}
		p, exists, err := s.podStore.GetByKey(watchedPod)
		if err != nil {
			return nil, err
		}
		if !exists {
			status[watchedPod] = doesNotExist
			continue
		}
		if p, ok := p.(*corev1.Pod); ok {
			status[watchedPod] = p.Status.Phase
		}
	}
	return status, nil
}
<commit_msg>fix: stop pod watcher<commit_after>package bootkube

import (
	"context"
	"fmt"
	"reflect"
	"strings"
	"time"

	"github.com/golang/glog"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/clientcmd"
)

const (
	doesNotExist = "DoesNotExist"
)

func WaitUntilPodsRunning(c clientcmd.ClientConfig, pods []string, timeout time.Duration) error {
	sc, err := NewStatusController(c, pods)
	if err != nil {
		return err
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	sc.Run(ctx)

	if err := wait.Poll(5*time.Second, timeout, sc.AllRunning); err != nil {
		return fmt.Errorf("error while checking pod status: %v", err)
	}

	UserOutput("All self-hosted control plane components successfully started\n")
	return nil
}

type statusController struct {
	client        kubernetes.Interface
	podStore      cache.Store
	watchPods     []string
	lastPodPhases map[string]corev1.PodPhase
}

func NewStatusController(c clientcmd.ClientConfig, pods []string) (*statusController, error) {
	config, err := c.ClientConfig()
	if err != nil {
		return nil, err
	}
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	return &statusController{client: client, watchPods: pods}, nil
}

func (s *statusController) Run(ctx context.Context) {
	// TODO(yifan): Be more explicit about the labels so that we don't just
	// reply on the prefix of the pod name when looking for the pods we are interested.
	// E.g. For a scheduler pod, we will look for pods that has label `tier=control-plane`
	// and `component=kube-scheduler`.
	options := metav1.ListOptions{}
	podStore, podController := cache.NewInformer(
		&cache.ListWatch{
			ListFunc: func(lo metav1.ListOptions) (runtime.Object, error) {
				return s.client.CoreV1().Pods("").List(ctx, options)
			},
			WatchFunc: func(lo metav1.ListOptions) (watch.Interface, error) {
				return s.client.CoreV1().Pods("").Watch(ctx, options)
			},
		},
		&corev1.Pod{},
		30*time.Minute,
		cache.ResourceEventHandlerFuncs{},
	)
	s.podStore = podStore
	go podController.Run(ctx.Done())
}

func (s *statusController) AllRunning() (bool, error) {
	ps, err := s.PodStatus()
	if err != nil {
		glog.Infof("Error retriving pod statuses: %v", err)
		return false, nil
	}

	if s.lastPodPhases == nil {
		s.lastPodPhases = ps
	}

	// use lastPodPhases to print only pods whose phase has changed
	changed := !reflect.DeepEqual(ps, s.lastPodPhases)
	s.lastPodPhases = ps

	running := true
	for p, s := range ps {
		if changed {
			UserOutput("\tPod Status:%24s\t%s\n", p, s)
		}
		if s != corev1.PodRunning {
			running = false
		}
	}
	return running, nil
}

func (s *statusController) PodStatus() (map[string]corev1.PodPhase, error) {
	status := make(map[string]corev1.PodPhase)

	podNames := s.podStore.ListKeys()
	for _, watchedPod := range s.watchPods {
		// Pod names are suffixed with random data. Match on prefix
		for _, pn := range podNames {
			if strings.HasPrefix(pn, watchedPod) {
				watchedPod = pn
				break
			}
		}
		p, exists, err := s.podStore.GetByKey(watchedPod)
		if err != nil {
			return nil, err
		}
		if !exists {
			status[watchedPod] = doesNotExist
			continue
		}
		if p, ok := p.(*corev1.Pod); ok {
			status[watchedPod] = p.Status.Phase
		}
	}
	return status, nil
}
<|endoftext|>