<commit_before>package git

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"testing"

	"github.com/goreleaser/goreleaser/config"
	"github.com/goreleaser/goreleaser/context"
	"github.com/stretchr/testify/assert"
)

func TestDescription(t *testing.T) {
	assert.NotEmpty(t, Pipe{}.Description())
}

func TestValidVersion(t *testing.T) {
	var assert = assert.New(t)

	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.NoError(Pipe{}.Run(ctx))
	assert.NotEmpty(ctx.Git.CurrentTag)
	assert.NotEmpty(ctx.Git.PreviousTag)
	assert.NotEmpty(ctx.Git.Diff)
}

func TestNotAGitFolder(t *testing.T) {
	var assert = assert.New(t)
	_, back := createAndChdir(t)
	defer back()
	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.Error(Pipe{}.Run(ctx))
}

func TestSingleCommit(t *testing.T) {
	var assert = assert.New(t)
	_, back := createAndChdir(t)
	defer back()
	assert.NoError(exec.Command("git", "init").Run())
	assert.NoError(exec.Command("git", "commit", "--allow-empty", "-m", "commit1").Run())
	assert.NoError(exec.Command("git", "tag", "v0.0.1").Run())
	out, err := git("log")
	assert.NoError(err)
	fmt.Print("git log:\n", out)
	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.NoError(Pipe{}.Run(ctx))
	assert.Equal("v0.0.1", ctx.Git.CurrentTag)
}

func TestNewRepository(t *testing.T) {
	var assert = assert.New(t)
	_, back := createAndChdir(t)
	defer back()
	assert.NoError(exec.Command("git", "init").Run())
	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.Error(Pipe{}.Run(ctx))
}

func TestInvalidTagFormat(t *testing.T) {
	var assert = assert.New(t)
	_, back := createAndChdir(t)
	defer back()
	assert.NoError(exec.Command("git", "init").Run())
	assert.NoError(exec.Command("git", "commit", "--allow-empty", "-m", "commit2").Run())
	assert.NoError(exec.Command("git", "tag", "sadasd").Run())
	out, err := git("log")
	assert.NoError(err)
	fmt.Print("git log:\n", out)
	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.EqualError(Pipe{}.Run(ctx), "sadasd is not in a valid version format")
	assert.Equal("sadasd", ctx.Git.CurrentTag)
}

func createAndChdir(t *testing.T) (current string, back func()) {
	var assert = assert.New(t)
	folder, err := ioutil.TempDir("", "goreleasertest")
	assert.NoError(err)
	previous, err := os.Getwd()
	assert.NoError(err)
	assert.NoError(os.Chdir(folder))
	return folder, func() {
		assert.NoError(os.Chdir(previous))
	}
}
<commit_msg>more asserts and logs<commit_after>package git

import (
	"fmt"
	"io/ioutil"
	"os"
	"testing"

	"github.com/goreleaser/goreleaser/config"
	"github.com/goreleaser/goreleaser/context"
	"github.com/stretchr/testify/assert"
)

func TestDescription(t *testing.T) {
	assert.NotEmpty(t, Pipe{}.Description())
}

func TestValidVersion(t *testing.T) {
	var assert = assert.New(t)

	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.NoError(Pipe{}.Run(ctx))
	assert.NotEmpty(ctx.Git.CurrentTag)
	assert.NotEmpty(ctx.Git.PreviousTag)
	assert.NotEmpty(ctx.Git.Diff)
}

func TestNotAGitFolder(t *testing.T) {
	var assert = assert.New(t)
	_, back := createAndChdir(t)
	defer back()
	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.Error(Pipe{}.Run(ctx))
}

func TestSingleCommit(t *testing.T) {
	var assert = assert.New(t)
	_, back := createAndChdir(t)
	defer back()
	gitInit(t)
	gitCommit(t, "commit1")
	gitTag(t, "v0.0.1")
	gitLog(t)
	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.NoError(Pipe{}.Run(ctx))
	assert.Equal("v0.0.1", ctx.Git.CurrentTag)
}

func TestNewRepository(t *testing.T) {
	var assert = assert.New(t)
	_, back := createAndChdir(t)
	defer back()
	gitInit(t)
	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.Error(Pipe{}.Run(ctx))
}

func TestInvalidTagFormat(t *testing.T) {
	var assert = assert.New(t)
	_, back := createAndChdir(t)
	defer back()
	gitInit(t)
	gitCommit(t, "commit2")
	gitTag(t, "sadasd")
	gitLog(t)
	var ctx = &context.Context{
		Config: config.Project{},
	}
	assert.EqualError(Pipe{}.Run(ctx), "sadasd is not in a valid version format")
	assert.Equal("sadasd", ctx.Git.CurrentTag)
}

//
// helper functions
//

func createAndChdir(t *testing.T) (current string, back func()) {
	var assert = assert.New(t)
	folder, err := ioutil.TempDir("", "goreleasertest")
	assert.NoError(err)
	previous, err := os.Getwd()
	assert.NoError(err)
	assert.NoError(os.Chdir(folder))
	return folder, func() {
		assert.NoError(os.Chdir(previous))
	}
}

func gitInit(t *testing.T) {
	var assert = assert.New(t)
	out, err := git("init")
	assert.NoError(err)
	assert.Contains(out, "Initialized empty Git repository")
}

func gitCommit(t *testing.T, msg string) {
	var assert = assert.New(t)
	out, err := git("commit", "--allow-empty", "-m", msg)
	assert.NoError(err)
	assert.Contains(out, "master", msg)
}

func gitTag(t *testing.T, tag string) {
	var assert = assert.New(t)
	out, err := git("tag", tag)
	assert.NoError(err)
	assert.Empty(out)
}

func gitLog(t *testing.T) {
	var assert = assert.New(t)
	out, err := git("log")
	assert.NoError(err)
	assert.NotEmpty(out)
	fmt.Print("\n\ngit log output:\n", out)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package testing

import (
	"fmt"
	"mime"
	"os"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
)

var (
	testCodecMediaType        string
	testStorageCodecMediaType string
)

// TestCodec returns the codec for the API version to test against, as set by the
// KUBE_TEST_API_TYPE env var.
func TestCodec(codecs runtimeserializer.CodecFactory, gvs ...schema.GroupVersion) runtime.Codec {
	if len(testCodecMediaType) != 0 {
		serializerInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), testCodecMediaType)
		if !ok {
			panic(fmt.Sprintf("no serializer for %s", testCodecMediaType))
		}
		return codecs.CodecForVersions(serializerInfo.Serializer, codecs.UniversalDeserializer(), schema.GroupVersions(gvs), nil)
	}
	return codecs.LegacyCodec(gvs...)
}

// TestStorageCodec returns the codec for the API version to test against used in storage, as set by the
// KUBE_TEST_API_STORAGE_TYPE env var.
func TestStorageCodec(codecs runtimeserializer.CodecFactory, gvs ...schema.GroupVersion) runtime.Codec {
	if len(testStorageCodecMediaType) != 0 {
		serializerInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), testStorageCodecMediaType)
		if !ok {
			panic(fmt.Sprintf("no serializer for %s", testStorageCodecMediaType))
		}

		// etcd2 only supports string data - we must wrap any result before returning
		// TODO: remove for etcd3 / make parameterizable
		serializer := serializerInfo.Serializer
		if !serializerInfo.EncodesAsText {
			serializer = runtime.NewBase64Serializer(serializer, serializer)
		}

		decoder := recognizer.NewDecoder(serializer, codecs.UniversalDeserializer())
		return codecs.CodecForVersions(serializer, decoder, schema.GroupVersions(gvs), nil)

	}
	return codecs.LegacyCodec(gvs...)
}

func init() {
	var err error
	if apiMediaType := os.Getenv("KUBE_TEST_API_TYPE"); len(apiMediaType) > 0 {
		testCodecMediaType, _, err = mime.ParseMediaType(apiMediaType)
		if err != nil {
			panic(err)
		}
	}

	if storageMediaType := os.Getenv("KUBE_TEST_API_STORAGE_TYPE"); len(storageMediaType) > 0 {
		testStorageCodecMediaType, _, err = mime.ParseMediaType(storageMediaType)
		if err != nil {
			panic(err)
		}
	}
}
<commit_msg>add methods to apimachinery to easy unit testing<commit_after>/*
Copyright 2017 The Kubernetes Authors.

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

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

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

package apitesting

import (
	"fmt"
	"mime"
	"os"

	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
	"k8s.io/apimachinery/pkg/runtime/serializer/recognizer"
)

var (
	testCodecMediaType        string
	testStorageCodecMediaType string
)

// TestCodec returns the codec for the API version to test against, as set by the
// KUBE_TEST_API_TYPE env var.
func TestCodec(codecs runtimeserializer.CodecFactory, gvs ...schema.GroupVersion) runtime.Codec {
	if len(testCodecMediaType) != 0 {
		serializerInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), testCodecMediaType)
		if !ok {
			panic(fmt.Sprintf("no serializer for %s", testCodecMediaType))
		}
		return codecs.CodecForVersions(serializerInfo.Serializer, codecs.UniversalDeserializer(), schema.GroupVersions(gvs), nil)
	}
	return codecs.LegacyCodec(gvs...)
}

// TestStorageCodec returns the codec for the API version to test against used in storage, as set by the
// KUBE_TEST_API_STORAGE_TYPE env var.
func TestStorageCodec(codecs runtimeserializer.CodecFactory, gvs ...schema.GroupVersion) runtime.Codec {
	if len(testStorageCodecMediaType) != 0 {
		serializerInfo, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), testStorageCodecMediaType)
		if !ok {
			panic(fmt.Sprintf("no serializer for %s", testStorageCodecMediaType))
		}

		// etcd2 only supports string data - we must wrap any result before returning
		// TODO: remove for etcd3 / make parameterizable
		serializer := serializerInfo.Serializer
		if !serializerInfo.EncodesAsText {
			serializer = runtime.NewBase64Serializer(serializer, serializer)
		}

		decoder := recognizer.NewDecoder(serializer, codecs.UniversalDeserializer())
		return codecs.CodecForVersions(serializer, decoder, schema.GroupVersions(gvs), nil)

	}
	return codecs.LegacyCodec(gvs...)
}

func init() {
	var err error
	if apiMediaType := os.Getenv("KUBE_TEST_API_TYPE"); len(apiMediaType) > 0 {
		testCodecMediaType, _, err = mime.ParseMediaType(apiMediaType)
		if err != nil {
			panic(err)
		}
	}

	if storageMediaType := os.Getenv("KUBE_TEST_API_STORAGE_TYPE"); len(storageMediaType) > 0 {
		testStorageCodecMediaType, _, err = mime.ParseMediaType(storageMediaType)
		if err != nil {
			panic(err)
		}
	}
}

// InstallOrDieFunc mirrors install functions that require success
type InstallOrDieFunc func(scheme *runtime.Scheme)

// SchemeForInstallOrDie builds a simple test scheme and codecfactory pair for easy unit testing from higher level install methods
func SchemeForInstallOrDie(installFns ...InstallOrDieFunc) (*runtime.Scheme, runtimeserializer.CodecFactory) {
	scheme := runtime.NewScheme()
	codecFactory := runtimeserializer.NewCodecFactory(scheme)
	for _, installFn := range installFns {
		installFn(scheme)
	}

	return scheme, codecFactory
}

// InstallFunc mirrors install functions that can return an error
type InstallFunc func(scheme *runtime.Scheme) error

// SchemeForOrDie builds a simple test scheme and codecfactory pair for easy unit testing from the bare registration methods.
func SchemeForOrDie(installFns ...InstallFunc) (*runtime.Scheme, runtimeserializer.CodecFactory) {
	scheme := runtime.NewScheme()
	codecFactory := runtimeserializer.NewCodecFactory(scheme)
	for _, installFn := range installFns {
		if err := installFn(scheme); err != nil {
			panic(err)
		}
	}

	return scheme, codecFactory
}
<|endoftext|>


<commit_before>package apl

import (
	"fmt"
	"github.com/dghubble/sling"
	"net/http"
)

// StackService is the service object for stack operations
type StackService struct {
	sling    *sling.Sling
	endpoint string
}

// NewStacksService return a new StackService
func NewStacksService(sling *sling.Sling) *StackService {
	return &StackService{
		sling:    sling,
		endpoint: "stacks",
	}
}

// Stack represents a stack row
type Stack struct {
	ID             string      `json:"id,omitempty"`
	Name           string      `json:"name"`
	VersionNumber  int         `json:"version_number,omitempty"`
	ReleaseNumber  int         `json:"release_number,omitempty"`
	Project        interface{} `json:"project"`
	StackVersions  interface{} `json:"stack_versions"`
	StackArtifacts interface{} `json:"stack_artifacts"`

	CreatedByUser `json:"created_by_user"`
	LastModified  string `json:"last_modified"`
	CreatedTime   string `json:"created_time"`
}

// StackCreateInput is used for the create of stacks
type StackCreateInput struct {
	ID             string      `json:"id,omitempty"`
	Name           string      `json:"name"`
	VersionNumber  int         `json:"version_number,omitempty"`
	ReleaseNumber  int         `json:"release_number,omitempty"`
	Project        interface{} `json:"project,omitempty"`
	StackVersions  interface{} `json:"stack_versions,omitempty"`
	StackArtifacts interface{} `json:"stack_artifacts,omitempty"`
}

// StackUpdateInput is used for the update of stacks
type StackUpdateInput struct {
	Name           string      `json:"name"`
	VersionNumber  int         `json:"version_number,omitempty"`
	ReleaseNumber  int         `json:"release_number,omitempty"`
	Project        interface{} `json:"project,omitempty"`
	StackVersions  interface{} `json:"stack_versions,omitempty"`
	StackArtifacts interface{} `json:"stack_artifacts,omitempty"`
}

// StackParams filter parameters used in list operations
type StackParams struct {
	Name          string `url:"name,omitempty"`
	VersionNumber int    `url:"version_number,omitempty"`
	ReleaseNumber int    `url:"release_number,omitempty"`
}

// List gets a list of stacks with optional filter params
func (c *StackService) List(params *StackParams) ([]Stack, *http.Response, error) {
	output := &struct {
		Data []Stack `json:"data"`
	}{}
	resp, err := doList(c.sling, c.endpoint, params, output)
	return output.Data, resp, err
}

// Get get a stack for the id specified
func (c *StackService) Get(id string) (Stack, *http.Response, error) {
	output := &struct {
		Data Stack `json:"data"`
	}{}
	path := fmt.Sprintf("%s/%s", c.endpoint, id)
	resp, err := doGet(c.sling, path, output)
	return output.Data, resp, err
}

// Create will create a stack
func (c *StackService) Create(input *StackCreateInput) (CreateResult, *http.Response, error) {
	return doCreate(c.sling, c.endpoint, input)
}

// Update will update a stack for the id specified
func (c *StackService) Update(id string, input *StackUpdateInput) (ModifyResult, *http.Response, error) {
	path := fmt.Sprintf("%s/%s", c.endpoint, id)
	return doUpdate(c.sling, path, input)
}

// Delete will delete the stack for the id specified
func (c *StackService) Delete(id string) (ModifyResult, *http.Response, error) {
	path := fmt.Sprintf("%s/%s", c.endpoint, id)
	return doDelete(c.sling, path)
}
<commit_msg>updated stackcreateinput<commit_after>package apl

import (
	"fmt"
	"github.com/dghubble/sling"
	"net/http"
)

// StackService is the service object for stack operations
type StackService struct {
	sling    *sling.Sling
	endpoint string
}

// NewStacksService return a new StackService
func NewStacksService(sling *sling.Sling) *StackService {
	return &StackService{
		sling:    sling,
		endpoint: "stacks",
	}
}

// Stack represents a stack row
type Stack struct {
	ID             string      `json:"id,omitempty"`
	Name           string      `json:"name"`
	VersionNumber  int         `json:"version_number,omitempty"`
	ReleaseNumber  int         `json:"release_number,omitempty"`
	Project        interface{} `json:"project"`
	StackVersions  interface{} `json:"stack_versions"`
	StackArtifacts interface{} `json:"stack_artifacts"`

	CreatedByUser `json:"created_by_user"`
	LastModified  string `json:"last_modified"`
	CreatedTime   string `json:"created_time"`
}

// StackCreateInput is used for the create of stacks
type StackCreateInput struct {
	ID             string      `json:"id,omitempty"`
    Name           string      `json:"name"`
    MetaData       interface{} `json:"meta_data"`
    ProjectID      string      `json:"project_id"`
    UseVersion     int         `json:"use_version"`
    Components     interface{} `json:"components"`
}

// StackUpdateInput is used for the update of stacks
type StackUpdateInput struct {
	Name           string      `json:"name"`
	VersionNumber  int         `json:"version_number,omitempty"`
	ReleaseNumber  int         `json:"release_number,omitempty"`
	Project        interface{} `json:"project,omitempty"`
	StackVersions  interface{} `json:"stack_versions,omitempty"`
	StackArtifacts interface{} `json:"stack_artifacts,omitempty"`
}

// StackParams filter parameters used in list operations
type StackParams struct {
	Name          string `url:"name,omitempty"`
	VersionNumber int    `url:"version_number,omitempty"`
	ReleaseNumber int    `url:"release_number,omitempty"`
}

// List gets a list of stacks with optional filter params
func (c *StackService) List(params *StackParams) ([]Stack, *http.Response, error) {
	output := &struct {
		Data []Stack `json:"data"`
	}{}
	resp, err := doList(c.sling, c.endpoint, params, output)
	return output.Data, resp, err
}

// Get get a stack for the id specified
func (c *StackService) Get(id string) (Stack, *http.Response, error) {
	output := &struct {
		Data Stack `json:"data"`
	}{}
	path := fmt.Sprintf("%s/%s", c.endpoint, id)
	resp, err := doGet(c.sling, path, output)
	return output.Data, resp, err
}

// Create will create a stack
func (c *StackService) Create(input *StackCreateInput) (CreateResult, *http.Response, error) {
	return doCreate(c.sling, c.endpoint, input)
}

// Update will update a stack for the id specified
func (c *StackService) Update(id string, input *StackUpdateInput) (ModifyResult, *http.Response, error) {
	path := fmt.Sprintf("%s/%s", c.endpoint, id)
	return doUpdate(c.sling, path, input)
}

// Delete will delete the stack for the id specified
func (c *StackService) Delete(id string) (ModifyResult, *http.Response, error) {
	path := fmt.Sprintf("%s/%s", c.endpoint, id)
	return doDelete(c.sling, path)
}
<|endoftext|>


<commit_before>package cluster

import (
	"fmt"
	"net/http"

	log "github.com/Sirupsen/logrus"
	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
	"github.com/coreos/kube-etcd-controller/pkg/util/constants"
	"github.com/coreos/kube-etcd-controller/pkg/util/etcdutil"
	"github.com/coreos/kube-etcd-controller/pkg/util/k8sutil"
	"golang.org/x/net/context"
	"k8s.io/kubernetes/pkg/util/wait"
)

// reconcile reconciles
// - the members in the cluster view with running pods in Kubernetes.
// - the members and expect size of cluster.
//
// Definitions:
// - running pods in k8s cluster
// - members in controller knowledge
// Steps:
// 1. Remove all pods from running set that does not belong to member set.
// 2. L consist of remaining pods of runnings
// 3. If L = members, the current state matches the membership state. END.
// 4. If len(L) < len(members)/2 + 1, quorum lost. Go to recovery process (TODO).
// 5. Add one missing member. END.
func (c *Cluster) reconcile(running etcdutil.MemberSet) error {
	log.Println("Reconciling:")
	defer func() {
		log.Println("Finish Reconciling")
	}()

	log.Infof("Running pods: %s", running)
	if len(c.members) == 0 {
		cfg := clientv3.Config{
			Endpoints:   running.ClientURLs(),
			DialTimeout: constants.DefaultDialTimeout,
		}
		etcdcli, err := clientv3.New(cfg)
		if err != nil {
			return err
		}
		if err := etcdutil.WaitMemberReady(etcdcli, constants.DefaultDialTimeout); err != nil {
			return err
		}
		c.updateMembers(etcdcli)
	}

	log.Println("Expected membership:", c.members)

	unknownMembers := running.Diff(c.members)
	if unknownMembers.Size() > 0 {
		log.Println("Removing unexpected pods:", unknownMembers)
		for _, m := range unknownMembers {
			if err := c.removePodAndService(m.Name); err != nil {
				return err
			}
		}
	}
	L := running.Diff(unknownMembers)

	if L.Size() == c.members.Size() {
		return c.resize()
	}

	if L.Size() < c.members.Size()/2+1 {
		log.Println("Disaster recovery")
		return c.disasterRecovery(L)
	}

	log.Println("Recovering one member")
	toRecover := c.members.Diff(L).PickOne()

	if err := c.removeMember(toRecover); err != nil {
		return err
	}
	return c.resize()
}

func (c *Cluster) resize() error {
	if c.members.Size() == c.spec.Size {
		return nil
	}

	if c.members.Size() < c.spec.Size {
		return c.addOneMember()
	}

	return c.removeOneMember()
}

func (c *Cluster) addOneMember() error {
	cfg := clientv3.Config{
		Endpoints:   c.members.ClientURLs(),
		DialTimeout: constants.DefaultDialTimeout,
	}
	etcdcli, err := clientv3.New(cfg)
	if err != nil {
		return err
	}
	newMemberName := fmt.Sprintf("%s-%04d", c.name, c.idCounter)
	newMember := &etcdutil.Member{Name: newMemberName}
	var id uint64
	// Could have "unhealthy cluster" due to 5 second strict check. Retry.
	err = wait.Poll(1*time.Second, 20*time.Second, func() (done bool, err error) {
		ctx, _ := context.WithTimeout(context.Background(), constants.DefaultRequestTimeout)
		resp, err := etcdcli.MemberAdd(ctx, []string{newMember.PeerAddr()})
		if err != nil {
			if err == rpctypes.ErrUnhealthy {
				return false, nil
			}
			return false, fmt.Errorf("etcdcli failed to add one member: %v", err)
		}
		id = resp.Member.ID
		return true, nil
	})
	if err != nil {
		return err
	}
	newMember.ID = id
	c.members.Add(newMember)

	if err := c.createPodAndService(c.members, newMember, "existing", false); err != nil {
		return err
	}
	c.idCounter++
	log.Printf("added member, cluster: %s", c.members.PeerURLPairs())
	return nil
}

func (c *Cluster) removeOneMember() error {
	return c.removeMember(c.members.PickOne())
}

func (c *Cluster) removeMember(toRemove *etcdutil.Member) error {
	cfg := clientv3.Config{
		Endpoints:   c.members.ClientURLs(),
		DialTimeout: constants.DefaultDialTimeout,
	}
	etcdcli, err := clientv3.New(cfg)
	if err != nil {
		return err
	}

	clustercli := clientv3.NewCluster(etcdcli)
	ctx, _ := context.WithTimeout(context.Background(), constants.DefaultRequestTimeout)
	if _, err := clustercli.MemberRemove(ctx, toRemove.ID); err != nil {
		return fmt.Errorf("etcdcli failed to remove one member: %v", err)
	}
	c.members.Remove(toRemove.Name)
	if err := c.removePodAndService(toRemove.Name); err != nil {
		return err
	}
	log.Printf("removed member (%v) with ID (%d)", toRemove.Name, toRemove.ID)
	return nil
}

func (c *Cluster) disasterRecovery(left etcdutil.MemberSet) error {
	httpClient := c.kclient.RESTClient.Client
	resp, err := httpClient.Get(fmt.Sprintf("http://%s/backupnow", k8sutil.MakeBackupHostPort(c.name)))
	if err != nil {
		log.Errorf("requesting backupnow failed: %v", err)
		return err
	}
	if resp.StatusCode != http.StatusOK {
		panic("TODO: handle failure of backupnow request")
	}
	log.Info("Made a latest backup successfully")

	for _, m := range left {
		err := c.removePodAndService(m.Name)
		if err != nil {
			return err
		}
	}
	c.members = nil
	return c.restoreSeedMember()
}
<commit_msg>fix import<commit_after>package cluster

import (
	"fmt"
	"net/http"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
	"github.com/coreos/kube-etcd-controller/pkg/util/constants"
	"github.com/coreos/kube-etcd-controller/pkg/util/etcdutil"
	"github.com/coreos/kube-etcd-controller/pkg/util/k8sutil"
	"golang.org/x/net/context"
	"k8s.io/kubernetes/pkg/util/wait"
)

// reconcile reconciles
// - the members in the cluster view with running pods in Kubernetes.
// - the members and expect size of cluster.
//
// Definitions:
// - running pods in k8s cluster
// - members in controller knowledge
// Steps:
// 1. Remove all pods from running set that does not belong to member set.
// 2. L consist of remaining pods of runnings
// 3. If L = members, the current state matches the membership state. END.
// 4. If len(L) < len(members)/2 + 1, quorum lost. Go to recovery process (TODO).
// 5. Add one missing member. END.
func (c *Cluster) reconcile(running etcdutil.MemberSet) error {
	log.Println("Reconciling:")
	defer func() {
		log.Println("Finish Reconciling")
	}()

	log.Infof("Running pods: %s", running)
	if len(c.members) == 0 {
		cfg := clientv3.Config{
			Endpoints:   running.ClientURLs(),
			DialTimeout: constants.DefaultDialTimeout,
		}
		etcdcli, err := clientv3.New(cfg)
		if err != nil {
			return err
		}
		if err := etcdutil.WaitMemberReady(etcdcli, constants.DefaultDialTimeout); err != nil {
			return err
		}
		c.updateMembers(etcdcli)
	}

	log.Println("Expected membership:", c.members)

	unknownMembers := running.Diff(c.members)
	if unknownMembers.Size() > 0 {
		log.Println("Removing unexpected pods:", unknownMembers)
		for _, m := range unknownMembers {
			if err := c.removePodAndService(m.Name); err != nil {
				return err
			}
		}
	}
	L := running.Diff(unknownMembers)

	if L.Size() == c.members.Size() {
		return c.resize()
	}

	if L.Size() < c.members.Size()/2+1 {
		log.Println("Disaster recovery")
		return c.disasterRecovery(L)
	}

	log.Println("Recovering one member")
	toRecover := c.members.Diff(L).PickOne()

	if err := c.removeMember(toRecover); err != nil {
		return err
	}
	return c.resize()
}

func (c *Cluster) resize() error {
	if c.members.Size() == c.spec.Size {
		return nil
	}

	if c.members.Size() < c.spec.Size {
		return c.addOneMember()
	}

	return c.removeOneMember()
}

func (c *Cluster) addOneMember() error {
	cfg := clientv3.Config{
		Endpoints:   c.members.ClientURLs(),
		DialTimeout: constants.DefaultDialTimeout,
	}
	etcdcli, err := clientv3.New(cfg)
	if err != nil {
		return err
	}
	newMemberName := fmt.Sprintf("%s-%04d", c.name, c.idCounter)
	newMember := &etcdutil.Member{Name: newMemberName}
	var id uint64
	// Could have "unhealthy cluster" due to 5 second strict check. Retry.
	err = wait.Poll(1*time.Second, 20*time.Second, func() (done bool, err error) {
		ctx, _ := context.WithTimeout(context.Background(), constants.DefaultRequestTimeout)
		resp, err := etcdcli.MemberAdd(ctx, []string{newMember.PeerAddr()})
		if err != nil {
			if err == rpctypes.ErrUnhealthy {
				return false, nil
			}
			return false, fmt.Errorf("etcdcli failed to add one member: %v", err)
		}
		id = resp.Member.ID
		return true, nil
	})
	if err != nil {
		return err
	}
	newMember.ID = id
	c.members.Add(newMember)

	if err := c.createPodAndService(c.members, newMember, "existing", false); err != nil {
		return err
	}
	c.idCounter++
	log.Printf("added member, cluster: %s", c.members.PeerURLPairs())
	return nil
}

func (c *Cluster) removeOneMember() error {
	return c.removeMember(c.members.PickOne())
}

func (c *Cluster) removeMember(toRemove *etcdutil.Member) error {
	cfg := clientv3.Config{
		Endpoints:   c.members.ClientURLs(),
		DialTimeout: constants.DefaultDialTimeout,
	}
	etcdcli, err := clientv3.New(cfg)
	if err != nil {
		return err
	}

	clustercli := clientv3.NewCluster(etcdcli)
	ctx, _ := context.WithTimeout(context.Background(), constants.DefaultRequestTimeout)
	if _, err := clustercli.MemberRemove(ctx, toRemove.ID); err != nil {
		return fmt.Errorf("etcdcli failed to remove one member: %v", err)
	}
	c.members.Remove(toRemove.Name)
	if err := c.removePodAndService(toRemove.Name); err != nil {
		return err
	}
	log.Printf("removed member (%v) with ID (%d)", toRemove.Name, toRemove.ID)
	return nil
}

func (c *Cluster) disasterRecovery(left etcdutil.MemberSet) error {
	httpClient := c.kclient.RESTClient.Client
	resp, err := httpClient.Get(fmt.Sprintf("http://%s/backupnow", k8sutil.MakeBackupHostPort(c.name)))
	if err != nil {
		log.Errorf("requesting backupnow failed: %v", err)
		return err
	}
	if resp.StatusCode != http.StatusOK {
		panic("TODO: handle failure of backupnow request")
	}
	log.Info("Made a latest backup successfully")

	for _, m := range left {
		err := c.removePodAndService(m.Name)
		if err != nil {
			return err
		}
	}
	c.members = nil
	return c.restoreSeedMember()
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Knative Authors

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

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

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

package environment

import (
	"context"
	"fmt"
	"sync"
	"testing"

	corev1 "k8s.io/api/core/v1"
	"knative.dev/reconciler-test/pkg/feature"
)

func NewGlobalEnvironment(ctx context.Context) GlobalEnvironment {

	fmt.Printf("level %s, state %s\n\n", l, s)

	return &MagicGlobalEnvironment{
		c:                ctx,
		RequirementLevel: *l,
		FeatureState:     *s,
	}
}

type MagicGlobalEnvironment struct {
	c context.Context

	RequirementLevel feature.Levels
	FeatureState     feature.States
}

type MagicEnvironment struct {
	c context.Context
	l feature.Levels
	s feature.States

	images           map[string]string
	namespace        string
	namespaceCreated bool
	refs             []corev1.ObjectReference
}

func (mr *MagicEnvironment) Reference(ref ...corev1.ObjectReference) {
	mr.refs = append(mr.refs, ref...)
}

func (mr *MagicEnvironment) References() []corev1.ObjectReference {
	return mr.refs
}

func (mr *MagicEnvironment) Finish() {
	mr.DeleteNamespaceIfNeeded()
}

func (mr *MagicGlobalEnvironment) Environment(opts ...EnvOpts) (context.Context, Environment) {
	images, err := ProduceImages()
	if err != nil {
		panic(err)
	}

	namespace := feature.MakeK8sNamePrefix(feature.AppendRandomString("rekt"))

	env := &MagicEnvironment{
		c:         mr.c,
		l:         mr.RequirementLevel,
		s:         mr.FeatureState,
		images:    images,
		namespace: namespace,
	}

	ctx := ContextWith(mr.c, env)

	for _, opt := range opts {
		if ctx, err = opt(ctx, env); err != nil {
			panic(err)
		}
	}

	if err := env.CreateNamespaceIfNeeded(); err != nil {
		panic(err)
	}

	return ctx, env
}

func (mr *MagicEnvironment) Images() map[string]string {
	return mr.images
}

func (mr *MagicEnvironment) TemplateConfig(base map[string]interface{}) map[string]interface{} {
	cfg := make(map[string]interface{})
	for k, v := range base {
		cfg[k] = v
	}
	cfg["namespace"] = mr.namespace
	return cfg
}

func (mr *MagicEnvironment) RequirementLevel() feature.Levels {
	return mr.l
}

func (mr *MagicEnvironment) FeatureState() feature.States {
	return mr.s
}

func (mr *MagicEnvironment) Namespace() string {
	return mr.namespace
}

func (mr *MagicEnvironment) Prerequisite(ctx context.Context, t *testing.T, f *feature.Feature) {
	t.Helper() // Helper marks the calling function as a test helper function.
	t.Run("Prerequisite", func(t *testing.T) {
		mr.Test(ctx, t, f)
	})
}

func (mr *MagicEnvironment) Test(ctx context.Context, t *testing.T, f *feature.Feature) {
	t.Helper() // Helper marks the calling function as a test helper function.

	steps := feature.CollapseSteps(f.Steps)

	for _, timing := range feature.Timings() {
		// do it the slow way first.
		wg := &sync.WaitGroup{}
		wg.Add(1)

		t.Run(timing.String(), func(t *testing.T) {
			t.Helper()      // Helper marks the calling function as a test helper function.
			defer wg.Done() // Outer wait.

			for _, s := range steps {
				// Skip if step phase is not running.
				if s.T != timing {
					continue
				}
				t.Run(s.TestName(), func(t *testing.T) {
					wg.Add(1)
					defer wg.Done()

					if mr.s&s.S == 0 {
						t.Skipf("%s features not enabled for testing", s.S)
					}
					if mr.l&s.L == 0 {
						t.Skipf("%s requirement not enabled for testing", s.L)
					}

					s := s

					t.Helper() // Helper marks the calling function as a test helper function.

					// Perform step.
					s.Fn(ctx, t)

				})
			}
		})

		wg.Wait()
	}
}

type envKey struct{}

func ContextWith(ctx context.Context, e Environment) context.Context {
	return context.WithValue(ctx, envKey{}, e)
}

func FromContext(ctx context.Context) Environment {
	if e, ok := ctx.Value(envKey{}).(Environment); ok {
		return e
	}
	panic("no Environment found in context")
}
<commit_msg>Context cancelling should be tied to the test execution (#96)<commit_after>/*
Copyright 2020 The Knative Authors

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

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

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

package environment

import (
	"context"
	"fmt"
	"sync"
	"testing"

	corev1 "k8s.io/api/core/v1"
	"knative.dev/reconciler-test/pkg/feature"
)

func NewGlobalEnvironment(ctx context.Context) GlobalEnvironment {

	fmt.Printf("level %s, state %s\n\n", l, s)

	return &MagicGlobalEnvironment{
		c:                ctx,
		RequirementLevel: *l,
		FeatureState:     *s,
	}
}

type MagicGlobalEnvironment struct {
	c context.Context

	RequirementLevel feature.Levels
	FeatureState     feature.States
}

type MagicEnvironment struct {
	c context.Context
	l feature.Levels
	s feature.States

	images           map[string]string
	namespace        string
	namespaceCreated bool
	refs             []corev1.ObjectReference
}

func (mr *MagicEnvironment) Reference(ref ...corev1.ObjectReference) {
	mr.refs = append(mr.refs, ref...)
}

func (mr *MagicEnvironment) References() []corev1.ObjectReference {
	return mr.refs
}

func (mr *MagicEnvironment) Finish() {
	mr.DeleteNamespaceIfNeeded()
}

func (mr *MagicGlobalEnvironment) Environment(opts ...EnvOpts) (context.Context, Environment) {
	images, err := ProduceImages()
	if err != nil {
		panic(err)
	}

	namespace := feature.MakeK8sNamePrefix(feature.AppendRandomString("rekt"))

	env := &MagicEnvironment{
		c:         mr.c,
		l:         mr.RequirementLevel,
		s:         mr.FeatureState,
		images:    images,
		namespace: namespace,
	}

	ctx := ContextWith(mr.c, env)

	for _, opt := range opts {
		if ctx, err = opt(ctx, env); err != nil {
			panic(err)
		}
	}

	if err := env.CreateNamespaceIfNeeded(); err != nil {
		panic(err)
	}

	return ctx, env
}

func (mr *MagicEnvironment) Images() map[string]string {
	return mr.images
}

func (mr *MagicEnvironment) TemplateConfig(base map[string]interface{}) map[string]interface{} {
	cfg := make(map[string]interface{})
	for k, v := range base {
		cfg[k] = v
	}
	cfg["namespace"] = mr.namespace
	return cfg
}

func (mr *MagicEnvironment) RequirementLevel() feature.Levels {
	return mr.l
}

func (mr *MagicEnvironment) FeatureState() feature.States {
	return mr.s
}

func (mr *MagicEnvironment) Namespace() string {
	return mr.namespace
}

func (mr *MagicEnvironment) Prerequisite(ctx context.Context, t *testing.T, f *feature.Feature) {
	t.Helper() // Helper marks the calling function as a test helper function.
	t.Run("Prerequisite", func(t *testing.T) {
		mr.Test(ctx, t, f)
	})
}

func (mr *MagicEnvironment) Test(ctx context.Context, t *testing.T, f *feature.Feature) {
	t.Helper() // Helper marks the calling function as a test helper function.

	steps := feature.CollapseSteps(f.Steps)

	for _, timing := range feature.Timings() {
		// do it the slow way first.
		wg := &sync.WaitGroup{}
		wg.Add(1)

		t.Run(timing.String(), func(t *testing.T) {
			t.Helper()      // Helper marks the calling function as a test helper function.
			defer wg.Done() // Outer wait.

			for _, s := range steps {
				// Skip if step phase is not running.
				if s.T != timing {
					continue
				}
				t.Run(s.TestName(), func(t *testing.T) {
					ctx, cancelFn := context.WithCancel(ctx)
					t.Cleanup(cancelFn)

					wg.Add(1)
					defer wg.Done()

					if mr.s&s.S == 0 {
						t.Skipf("%s features not enabled for testing", s.S)
					}
					if mr.l&s.L == 0 {
						t.Skipf("%s requirement not enabled for testing", s.L)
					}

					s := s

					t.Helper() // Helper marks the calling function as a test helper function.

					// Perform step.
					s.Fn(ctx, t)

				})
			}
		})

		wg.Wait()
	}
}

type envKey struct{}

func ContextWith(ctx context.Context, e Environment) context.Context {
	return context.WithValue(ctx, envKey{}, e)
}

func FromContext(ctx context.Context) Environment {
	if e, ok := ctx.Value(envKey{}).(Environment); ok {
		return e
	}
	panic("no Environment found in context")
}
<|endoftext|>


<commit_before>// Copyright 2018-2019 Authors of Cilium
//
// 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 k8s

import (
	"net"

	"github.com/cilium/cilium/pkg/loadbalancer"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/service"

	"github.com/davecgh/go-spew/spew"
	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/api/extensions/v1beta1"
)

// CacheAction is the type of action that was performed on the cache
type CacheAction int

const (
	// UpdateService reflects that the service was updated or added
	UpdateService CacheAction = iota

	// DeleteService reflects that the service was deleted
	DeleteService

	// UpdateIngress reflects that the ingress was updated or added
	UpdateIngress

	// DeleteIngress reflects that the ingress was deleted
	DeleteIngress
)

// String returns the cache action as a string
func (c CacheAction) String() string {
	switch c {
	case UpdateService:
		return "service-updated"
	case DeleteService:
		return "service-deleted"
	case UpdateIngress:
		return "ingress-updated"
	case DeleteIngress:
		return "ingress-deleted"
	default:
		return "unknown"
	}
}

// ServiceEvent is emitted via the Events channel of ServiceCache and describes
// the change that occurred in the cache
type ServiceEvent struct {
	// Action is the action that was performed in the cache
	Action CacheAction

	// ID is the identified of the service
	ID ServiceID

	// Service is the service structure
	Service *Service

	// Endpoints is the endpoints structured correlated with the service
	Endpoints *Endpoints
}

// ServiceCache is a list of services and ingresses correlated with the
// matching endpoints. The Events member will receive events as services and
// ingresses
type ServiceCache struct {
	mutex     lock.RWMutex
	services  map[ServiceID]*Service
	endpoints map[ServiceID]*Endpoints
	ingresses map[ServiceID]*Service

	// externalEndpoints is a list of additional service backends derived from source other than the local cluster
	externalEndpoints map[ServiceID]externalEndpoints

	Events chan ServiceEvent
}

// NewServiceCache returns a new ServiceCache
func NewServiceCache() ServiceCache {
	return ServiceCache{
		services:          map[ServiceID]*Service{},
		endpoints:         map[ServiceID]*Endpoints{},
		ingresses:         map[ServiceID]*Service{},
		externalEndpoints: map[ServiceID]externalEndpoints{},
		Events:            make(chan ServiceEvent, 128),
	}
}

// GetRandomBackendIP returns a random L3n4Addr that is backing the given Service ID.
func (s *ServiceCache) GetRandomBackendIP(svcID ServiceID) *loadbalancer.L3n4Addr {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	svc := s.services[svcID]
	if svc == nil {
		return nil
	}
	for _, port := range svc.Ports {
		return loadbalancer.NewL3n4Addr(port.Protocol, svc.FrontendIP, port.Port)
	}
	return nil
}

// UpdateService parses a Kubernetes service and adds or updates it in the
// ServiceCache. Returns the ServiceID unless the Kubernetes service could not
// be parsed and a bool to indicate whether the service was changed in the
// cache or not.
func (s *ServiceCache) UpdateService(k8sSvc *v1.Service) ServiceID {
	svcID, newService := ParseService(k8sSvc)
	if newService == nil {
		return svcID
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	if oldService, ok := s.services[svcID]; ok {
		if oldService.DeepEquals(newService) {
			return svcID
		}
	}

	s.services[svcID] = newService

	// Check if the corresponding Endpoints resource is already available
	endpoints, serviceReady := s.correlateEndpoints(svcID)
	if serviceReady {
		s.Events <- ServiceEvent{
			Action:    UpdateService,
			ID:        svcID,
			Service:   newService,
			Endpoints: endpoints,
		}
	}

	return svcID
}

// DeleteService parses a Kubernetes service and removes it from the
// ServiceCache
func (s *ServiceCache) DeleteService(k8sSvc *v1.Service) {
	svcID := ParseServiceID(k8sSvc)

	s.mutex.Lock()
	oldService, serviceOK := s.services[svcID]
	endpoints, _ := s.correlateEndpoints(svcID)
	delete(s.services, svcID)
	s.mutex.Unlock()

	if serviceOK {
		s.Events <- ServiceEvent{
			Action:    DeleteService,
			ID:        svcID,
			Service:   oldService,
			Endpoints: endpoints,
		}
	}
}

// UpdateEndpoints parses a Kubernetes endpoints and adds or updates it in the
// ServiceCache. Returns the ServiceID unless the Kubernetes endpoints could not
// be parsed and a bool to indicate whether the endpoints was changed in the
// cache or not.
func (s *ServiceCache) UpdateEndpoints(k8sEndpoints *v1.Endpoints) (ServiceID, *Endpoints) {
	svcID, newEndpoints := ParseEndpoints(k8sEndpoints)

	s.mutex.Lock()
	defer s.mutex.Unlock()

	if oldEndpoints, ok := s.endpoints[svcID]; ok {
		if oldEndpoints.DeepEquals(newEndpoints) {
			return svcID, newEndpoints
		}
	}

	s.endpoints[svcID] = newEndpoints

	// Check if the corresponding Endpoints resource is already available
	service, ok := s.services[svcID]
	endpoints, serviceReady := s.correlateEndpoints(svcID)
	if ok && serviceReady {
		s.Events <- ServiceEvent{
			Action:    UpdateService,
			ID:        svcID,
			Service:   service,
			Endpoints: endpoints,
		}
	}

	return svcID, newEndpoints
}

// DeleteEndpoints parses a Kubernetes endpoints and removes it from the
// ServiceCache
func (s *ServiceCache) DeleteEndpoints(k8sEndpoints *v1.Endpoints) ServiceID {
	svcID := ParseEndpointsID(k8sEndpoints)

	s.mutex.Lock()
	service, serviceOK := s.services[svcID]
	delete(s.endpoints, svcID)
	endpoints, serviceReady := s.correlateEndpoints(svcID)
	s.mutex.Unlock()

	if serviceOK {
		event := ServiceEvent{
			Action:    DeleteService,
			ID:        svcID,
			Service:   service,
			Endpoints: endpoints,
		}

		if serviceReady {
			event.Action = UpdateService
		}

		s.Events <- event
	}

	return svcID
}

// UpdateIngress parses a Kubernetes ingress and adds or updates it in the
// ServiceCache.
func (s *ServiceCache) UpdateIngress(ingress *v1beta1.Ingress, host net.IP) (ServiceID, error) {
	svcID, newService, err := ParseIngress(ingress, host)
	if err != nil {
		return svcID, err
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	if oldService, ok := s.ingresses[svcID]; ok {
		if oldService.DeepEquals(newService) {
			return svcID, nil
		}
	}

	s.ingresses[svcID] = newService

	s.Events <- ServiceEvent{
		Action:  UpdateIngress,
		ID:      svcID,
		Service: newService,
	}

	return svcID, nil
}

// DeleteIngress parses a Kubernetes ingress and removes it from the
// ServiceCache
func (s *ServiceCache) DeleteIngress(ingress *v1beta1.Ingress) {
	svcID := ParseIngressID(ingress)

	s.mutex.Lock()
	oldService, ok := s.ingresses[svcID]
	endpoints, _ := s.endpoints[svcID]
	delete(s.ingresses, svcID)
	s.mutex.Unlock()

	if ok {
		s.Events <- ServiceEvent{
			Action:    DeleteIngress,
			ID:        svcID,
			Service:   oldService,
			Endpoints: endpoints,
		}
	}
}

// FrontendList is the list of all k8s service frontends
type FrontendList map[string]struct{}

// LooseMatch returns true if the provided frontend is found in the
// FrontendList. If the frontend has a protocol value set, it only matches a
// k8s service with a matching protocol. If no protocol is set, any k8s service
// matching frontend IP and port is considered a match, regardless of protocol.
func (l FrontendList) LooseMatch(frontend loadbalancer.L3n4Addr) (exists bool) {
	switch frontend.Protocol {
	case loadbalancer.NONE:
		for _, protocol := range loadbalancer.AllProtocols {
			frontend.Protocol = protocol
			_, exists = l[frontend.StringWithProtocol()]
			if exists {
				return
			}
		}

	// If the protocol is set, perform an exact match
	default:
		_, exists = l[frontend.StringWithProtocol()]
	}
	return
}

// UniqueServiceFrontends returns all services known to the service cache as a
// map, indexed by the string representation of a loadbalancer.L3n4Addr
func (s *ServiceCache) UniqueServiceFrontends() FrontendList {
	uniqueFrontends := FrontendList{}

	s.mutex.RLock()
	defer s.mutex.RUnlock()

	for _, svc := range s.services {
		for _, p := range svc.Ports {
			address := loadbalancer.L3n4Addr{
				IP:     svc.FrontendIP,
				L4Addr: *p.L4Addr,
			}

			uniqueFrontends[address.StringWithProtocol()] = struct{}{}
		}
	}

	return uniqueFrontends
}

// correlateEndpoints builds a combined Endpoints of the local endpoints and
// all external endpoints if the service is marked as a global service. Also
// returns a boolean that indicates whether the service is ready to be plumbed,
// this is true if:
// IF If ta local endpoints resource is present. Regardless whether the
//    endpoints resource contains actual backends or not.
// OR Remote endpoints exist which correlate to the service.
func (s *ServiceCache) correlateEndpoints(id ServiceID) (*Endpoints, bool) {
	endpoints := newEndpoints()

	localEndpoints, hasLocalEndpoints := s.endpoints[id]
	if hasLocalEndpoints {
		for ip, e := range localEndpoints.Backends {
			endpoints.Backends[ip] = e
		}
	}

	svc, hasExternalService := s.services[id]
	if hasExternalService && svc.IncludeExternal {
		externalEndpoints, hasExternalEndpoints := s.externalEndpoints[id]
		if hasExternalEndpoints {
			for clusterName, remoteClusterEndpoints := range externalEndpoints.endpoints {
				if clusterName == option.Config.ClusterName {
					continue
				}

				for ip, e := range remoteClusterEndpoints.Backends {
					if _, ok := endpoints.Backends[ip]; ok {
						log.WithFields(logrus.Fields{
							logfields.K8sSvcName:   id.Name,
							logfields.K8sNamespace: id.Namespace,
							logfields.IPAddr:       ip,
							"cluster":              clusterName,
						}).Warning("Conflicting service backend IP")
					} else {
						endpoints.Backends[ip] = e
					}
				}
			}
		}
	}

	// Report the service as ready if a local endpoints object exists or if
	// external endpoints have have been identified
	return endpoints, hasLocalEndpoints || len(endpoints.Backends) > 0
}

// MergeExternalServiceUpdate merges a cluster service of a remote cluster into
// the local service cache. The service endpoints are stored as external endpoints
// and are correlated on demand with local services via correlateEndpoints().
func (s *ServiceCache) MergeExternalServiceUpdate(service *service.ClusterService) {
	id := ServiceID{Name: service.Name, Namespace: service.Namespace}
	scopedLog := log.WithFields(logrus.Fields{logfields.ServiceName: service.String()})

	// Ignore updates of own cluster
	if service.Cluster == option.Config.ClusterName {
		scopedLog.Debug("Not merging external service. Own cluster")
		return
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	externalEndpoints, ok := s.externalEndpoints[id]
	if !ok {
		externalEndpoints = newExternalEndpoints()
		s.externalEndpoints[id] = externalEndpoints
	}

	scopedLog.Debugf("Updating backends to %+v", service.Backends)
	externalEndpoints.endpoints[service.Cluster] = &Endpoints{
		Backends: service.Backends,
	}

	svc, ok := s.services[id]

	endpoints, serviceReady := s.correlateEndpoints(id)

	// Only send event notification if service is shared and ready.
	// External endpoints are still tracked but correlation will not happen
	// until the service is marked as shared.
	if ok && svc.Shared && serviceReady {
		s.Events <- ServiceEvent{
			Action:    UpdateService,
			ID:        id,
			Service:   svc,
			Endpoints: endpoints,
		}
	}
}

// MergeExternalServiceDelete merges the deletion of a cluster service in a
// remote cluster into the local service cache. The service endpoints are
// stored as external endpoints and are correlated on demand with local
// services via correlateEndpoints().
func (s *ServiceCache) MergeExternalServiceDelete(service *service.ClusterService) {
	scopedLog := log.WithFields(logrus.Fields{logfields.ServiceName: service.String()})
	id := ServiceID{Name: service.Name, Namespace: service.Namespace}

	// Ignore updates of own cluster
	if service.Cluster == option.Config.ClusterName {
		scopedLog.Debug("Not merging external service. Own cluster")
		return
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	externalEndpoints, ok := s.externalEndpoints[id]
	if ok {
		scopedLog.Debug("Deleting external endpoints")

		delete(externalEndpoints.endpoints, service.Cluster)

		svc, ok := s.services[id]

		endpoints, serviceReady := s.correlateEndpoints(id)

		// Only send event notification if service is shared. External
		// endpoints are still tracked but correlation will not happen
		// until the service is marked as shared.
		if ok && svc.Shared {
			event := ServiceEvent{
				Action:    UpdateService,
				ID:        id,
				Service:   svc,
				Endpoints: endpoints,
			}

			if !serviceReady {
				event.Action = DeleteService
			}

			s.Events <- event
		}
	} else {
		scopedLog.Debug("Received delete event for non-existing endpoints")
	}
}

// DebugStatus implements debug.StatusObject to provide debug status collection
// ability
func (s *ServiceCache) DebugStatus() string {
	s.mutex.RLock()
	str := spew.Sdump(s)
	s.mutex.RUnlock()
	return str
}
<commit_msg>pkg/k8s: add missing documentation in ServiceCache struct<commit_after>// Copyright 2018-2019 Authors of Cilium
//
// 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 k8s

import (
	"net"

	"github.com/cilium/cilium/pkg/loadbalancer"
	"github.com/cilium/cilium/pkg/lock"
	"github.com/cilium/cilium/pkg/logging/logfields"
	"github.com/cilium/cilium/pkg/option"
	"github.com/cilium/cilium/pkg/service"

	"github.com/davecgh/go-spew/spew"
	"github.com/sirupsen/logrus"
	"k8s.io/api/core/v1"
	"k8s.io/api/extensions/v1beta1"
)

// CacheAction is the type of action that was performed on the cache
type CacheAction int

const (
	// UpdateService reflects that the service was updated or added
	UpdateService CacheAction = iota

	// DeleteService reflects that the service was deleted
	DeleteService

	// UpdateIngress reflects that the ingress was updated or added
	UpdateIngress

	// DeleteIngress reflects that the ingress was deleted
	DeleteIngress
)

// String returns the cache action as a string
func (c CacheAction) String() string {
	switch c {
	case UpdateService:
		return "service-updated"
	case DeleteService:
		return "service-deleted"
	case UpdateIngress:
		return "ingress-updated"
	case DeleteIngress:
		return "ingress-deleted"
	default:
		return "unknown"
	}
}

// ServiceEvent is emitted via the Events channel of ServiceCache and describes
// the change that occurred in the cache
type ServiceEvent struct {
	// Action is the action that was performed in the cache
	Action CacheAction

	// ID is the identified of the service
	ID ServiceID

	// Service is the service structure
	Service *Service

	// Endpoints is the endpoints structured correlated with the service
	Endpoints *Endpoints
}

// ServiceCache is a list of services and ingresses correlated with the
// matching endpoints. The Events member will receive events as services and
// ingresses
type ServiceCache struct {
	Events chan ServiceEvent

	// mutex protects the maps below including the concurrent access of each
	// value.
	mutex     lock.RWMutex
	services  map[ServiceID]*Service
	endpoints map[ServiceID]*Endpoints
	ingresses map[ServiceID]*Service

	// externalEndpoints is a list of additional service backends derived from source other than the local cluster
	externalEndpoints map[ServiceID]externalEndpoints
}

// NewServiceCache returns a new ServiceCache
func NewServiceCache() ServiceCache {
	return ServiceCache{
		services:          map[ServiceID]*Service{},
		endpoints:         map[ServiceID]*Endpoints{},
		ingresses:         map[ServiceID]*Service{},
		externalEndpoints: map[ServiceID]externalEndpoints{},
		Events:            make(chan ServiceEvent, 128),
	}
}

// GetRandomBackendIP returns a random L3n4Addr that is backing the given Service ID.
func (s *ServiceCache) GetRandomBackendIP(svcID ServiceID) *loadbalancer.L3n4Addr {
	s.mutex.RLock()
	defer s.mutex.RUnlock()
	svc := s.services[svcID]
	if svc == nil {
		return nil
	}
	for _, port := range svc.Ports {
		return loadbalancer.NewL3n4Addr(port.Protocol, svc.FrontendIP, port.Port)
	}
	return nil
}

// UpdateService parses a Kubernetes service and adds or updates it in the
// ServiceCache. Returns the ServiceID unless the Kubernetes service could not
// be parsed and a bool to indicate whether the service was changed in the
// cache or not.
func (s *ServiceCache) UpdateService(k8sSvc *v1.Service) ServiceID {
	svcID, newService := ParseService(k8sSvc)
	if newService == nil {
		return svcID
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	if oldService, ok := s.services[svcID]; ok {
		if oldService.DeepEquals(newService) {
			return svcID
		}
	}

	s.services[svcID] = newService

	// Check if the corresponding Endpoints resource is already available
	endpoints, serviceReady := s.correlateEndpoints(svcID)
	if serviceReady {
		s.Events <- ServiceEvent{
			Action:    UpdateService,
			ID:        svcID,
			Service:   newService,
			Endpoints: endpoints,
		}
	}

	return svcID
}

// DeleteService parses a Kubernetes service and removes it from the
// ServiceCache
func (s *ServiceCache) DeleteService(k8sSvc *v1.Service) {
	svcID := ParseServiceID(k8sSvc)

	s.mutex.Lock()
	oldService, serviceOK := s.services[svcID]
	endpoints, _ := s.correlateEndpoints(svcID)
	delete(s.services, svcID)
	s.mutex.Unlock()

	if serviceOK {
		s.Events <- ServiceEvent{
			Action:    DeleteService,
			ID:        svcID,
			Service:   oldService,
			Endpoints: endpoints,
		}
	}
}

// UpdateEndpoints parses a Kubernetes endpoints and adds or updates it in the
// ServiceCache. Returns the ServiceID unless the Kubernetes endpoints could not
// be parsed and a bool to indicate whether the endpoints was changed in the
// cache or not.
func (s *ServiceCache) UpdateEndpoints(k8sEndpoints *v1.Endpoints) (ServiceID, *Endpoints) {
	svcID, newEndpoints := ParseEndpoints(k8sEndpoints)

	s.mutex.Lock()
	defer s.mutex.Unlock()

	if oldEndpoints, ok := s.endpoints[svcID]; ok {
		if oldEndpoints.DeepEquals(newEndpoints) {
			return svcID, newEndpoints
		}
	}

	s.endpoints[svcID] = newEndpoints

	// Check if the corresponding Endpoints resource is already available
	service, ok := s.services[svcID]
	endpoints, serviceReady := s.correlateEndpoints(svcID)
	if ok && serviceReady {
		s.Events <- ServiceEvent{
			Action:    UpdateService,
			ID:        svcID,
			Service:   service,
			Endpoints: endpoints,
		}
	}

	return svcID, newEndpoints
}

// DeleteEndpoints parses a Kubernetes endpoints and removes it from the
// ServiceCache
func (s *ServiceCache) DeleteEndpoints(k8sEndpoints *v1.Endpoints) ServiceID {
	svcID := ParseEndpointsID(k8sEndpoints)

	s.mutex.Lock()
	service, serviceOK := s.services[svcID]
	delete(s.endpoints, svcID)
	endpoints, serviceReady := s.correlateEndpoints(svcID)
	s.mutex.Unlock()

	if serviceOK {
		event := ServiceEvent{
			Action:    DeleteService,
			ID:        svcID,
			Service:   service,
			Endpoints: endpoints,
		}

		if serviceReady {
			event.Action = UpdateService
		}

		s.Events <- event
	}

	return svcID
}

// UpdateIngress parses a Kubernetes ingress and adds or updates it in the
// ServiceCache.
func (s *ServiceCache) UpdateIngress(ingress *v1beta1.Ingress, host net.IP) (ServiceID, error) {
	svcID, newService, err := ParseIngress(ingress, host)
	if err != nil {
		return svcID, err
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	if oldService, ok := s.ingresses[svcID]; ok {
		if oldService.DeepEquals(newService) {
			return svcID, nil
		}
	}

	s.ingresses[svcID] = newService

	s.Events <- ServiceEvent{
		Action:  UpdateIngress,
		ID:      svcID,
		Service: newService,
	}

	return svcID, nil
}

// DeleteIngress parses a Kubernetes ingress and removes it from the
// ServiceCache
func (s *ServiceCache) DeleteIngress(ingress *v1beta1.Ingress) {
	svcID := ParseIngressID(ingress)

	s.mutex.Lock()
	oldService, ok := s.ingresses[svcID]
	endpoints, _ := s.endpoints[svcID]
	delete(s.ingresses, svcID)
	s.mutex.Unlock()

	if ok {
		s.Events <- ServiceEvent{
			Action:    DeleteIngress,
			ID:        svcID,
			Service:   oldService,
			Endpoints: endpoints,
		}
	}
}

// FrontendList is the list of all k8s service frontends
type FrontendList map[string]struct{}

// LooseMatch returns true if the provided frontend is found in the
// FrontendList. If the frontend has a protocol value set, it only matches a
// k8s service with a matching protocol. If no protocol is set, any k8s service
// matching frontend IP and port is considered a match, regardless of protocol.
func (l FrontendList) LooseMatch(frontend loadbalancer.L3n4Addr) (exists bool) {
	switch frontend.Protocol {
	case loadbalancer.NONE:
		for _, protocol := range loadbalancer.AllProtocols {
			frontend.Protocol = protocol
			_, exists = l[frontend.StringWithProtocol()]
			if exists {
				return
			}
		}

	// If the protocol is set, perform an exact match
	default:
		_, exists = l[frontend.StringWithProtocol()]
	}
	return
}

// UniqueServiceFrontends returns all services known to the service cache as a
// map, indexed by the string representation of a loadbalancer.L3n4Addr
func (s *ServiceCache) UniqueServiceFrontends() FrontendList {
	uniqueFrontends := FrontendList{}

	s.mutex.RLock()
	defer s.mutex.RUnlock()

	for _, svc := range s.services {
		for _, p := range svc.Ports {
			address := loadbalancer.L3n4Addr{
				IP:     svc.FrontendIP,
				L4Addr: *p.L4Addr,
			}

			uniqueFrontends[address.StringWithProtocol()] = struct{}{}
		}
	}

	return uniqueFrontends
}

// correlateEndpoints builds a combined Endpoints of the local endpoints and
// all external endpoints if the service is marked as a global service. Also
// returns a boolean that indicates whether the service is ready to be plumbed,
// this is true if:
// IF If ta local endpoints resource is present. Regardless whether the
//    endpoints resource contains actual backends or not.
// OR Remote endpoints exist which correlate to the service.
func (s *ServiceCache) correlateEndpoints(id ServiceID) (*Endpoints, bool) {
	endpoints := newEndpoints()

	localEndpoints, hasLocalEndpoints := s.endpoints[id]
	if hasLocalEndpoints {
		for ip, e := range localEndpoints.Backends {
			endpoints.Backends[ip] = e
		}
	}

	svc, hasExternalService := s.services[id]
	if hasExternalService && svc.IncludeExternal {
		externalEndpoints, hasExternalEndpoints := s.externalEndpoints[id]
		if hasExternalEndpoints {
			for clusterName, remoteClusterEndpoints := range externalEndpoints.endpoints {
				if clusterName == option.Config.ClusterName {
					continue
				}

				for ip, e := range remoteClusterEndpoints.Backends {
					if _, ok := endpoints.Backends[ip]; ok {
						log.WithFields(logrus.Fields{
							logfields.K8sSvcName:   id.Name,
							logfields.K8sNamespace: id.Namespace,
							logfields.IPAddr:       ip,
							"cluster":              clusterName,
						}).Warning("Conflicting service backend IP")
					} else {
						endpoints.Backends[ip] = e
					}
				}
			}
		}
	}

	// Report the service as ready if a local endpoints object exists or if
	// external endpoints have have been identified
	return endpoints, hasLocalEndpoints || len(endpoints.Backends) > 0
}

// MergeExternalServiceUpdate merges a cluster service of a remote cluster into
// the local service cache. The service endpoints are stored as external endpoints
// and are correlated on demand with local services via correlateEndpoints().
func (s *ServiceCache) MergeExternalServiceUpdate(service *service.ClusterService) {
	id := ServiceID{Name: service.Name, Namespace: service.Namespace}
	scopedLog := log.WithFields(logrus.Fields{logfields.ServiceName: service.String()})

	// Ignore updates of own cluster
	if service.Cluster == option.Config.ClusterName {
		scopedLog.Debug("Not merging external service. Own cluster")
		return
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	externalEndpoints, ok := s.externalEndpoints[id]
	if !ok {
		externalEndpoints = newExternalEndpoints()
		s.externalEndpoints[id] = externalEndpoints
	}

	scopedLog.Debugf("Updating backends to %+v", service.Backends)
	externalEndpoints.endpoints[service.Cluster] = &Endpoints{
		Backends: service.Backends,
	}

	svc, ok := s.services[id]

	endpoints, serviceReady := s.correlateEndpoints(id)

	// Only send event notification if service is shared and ready.
	// External endpoints are still tracked but correlation will not happen
	// until the service is marked as shared.
	if ok && svc.Shared && serviceReady {
		s.Events <- ServiceEvent{
			Action:    UpdateService,
			ID:        id,
			Service:   svc,
			Endpoints: endpoints,
		}
	}
}

// MergeExternalServiceDelete merges the deletion of a cluster service in a
// remote cluster into the local service cache. The service endpoints are
// stored as external endpoints and are correlated on demand with local
// services via correlateEndpoints().
func (s *ServiceCache) MergeExternalServiceDelete(service *service.ClusterService) {
	scopedLog := log.WithFields(logrus.Fields{logfields.ServiceName: service.String()})
	id := ServiceID{Name: service.Name, Namespace: service.Namespace}

	// Ignore updates of own cluster
	if service.Cluster == option.Config.ClusterName {
		scopedLog.Debug("Not merging external service. Own cluster")
		return
	}

	s.mutex.Lock()
	defer s.mutex.Unlock()

	externalEndpoints, ok := s.externalEndpoints[id]
	if ok {
		scopedLog.Debug("Deleting external endpoints")

		delete(externalEndpoints.endpoints, service.Cluster)

		svc, ok := s.services[id]

		endpoints, serviceReady := s.correlateEndpoints(id)

		// Only send event notification if service is shared. External
		// endpoints are still tracked but correlation will not happen
		// until the service is marked as shared.
		if ok && svc.Shared {
			event := ServiceEvent{
				Action:    UpdateService,
				ID:        id,
				Service:   svc,
				Endpoints: endpoints,
			}

			if !serviceReady {
				event.Action = DeleteService
			}

			s.Events <- event
		}
	} else {
		scopedLog.Debug("Received delete event for non-existing endpoints")
	}
}

// DebugStatus implements debug.StatusObject to provide debug status collection
// ability
func (s *ServiceCache) DebugStatus() string {
	s.mutex.RLock()
	str := spew.Sdump(s)
	s.mutex.RUnlock()
	return str
}
<|endoftext|>


<commit_before>/*
Copyright 2019 Google LLC

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

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

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

package operations

import (
	"strings"
)

// JobLabels creates a label to find a job again.
// keys is recommended to be (name, kind, action)
func JobLabels(key string, parts ...string) map[string]string {
	return map[string]string{
		"pubsub.cloud.run/" + key: strings.Join(append([]string{"ops"}, parts...), "-"),
	}
}
<commit_msg>make it so that ops use short labels.<commit_after>/*
Copyright 2019 Google LLC

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

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

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

package operations

import (
	"knative.dev/pkg/kmeta"
	"strings"
)

// JobLabels creates a label to find a job again.
// keys is recommended to be (name, kind, action)
func JobLabels(key string, parts ...string) map[string]string {
	value := strings.Join(append(parts), "-")
	value = kmeta.ChildName(value, "ops")
	return map[string]string{
		"events.cloud.run/" + key: value,
	}
}
<|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 pod

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strings"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
)

// podStrategy implements behavior for Pods
// TODO: move to a pod specific package.
type podStrategy struct {
	runtime.ObjectTyper
	api.NameGenerator
}

// Strategy is the default logic that applies when creating and updating Pod
// objects via the REST API.
var Strategy = podStrategy{api.Scheme, api.SimpleNameGenerator}

// NamespaceScoped is true for pods.
func (podStrategy) NamespaceScoped() bool {
	return true
}

// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (podStrategy) PrepareForCreate(obj runtime.Object) {
	pod := obj.(*api.Pod)
	pod.Status = api.PodStatus{
		Phase: api.PodPending,
	}
}

// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (podStrategy) PrepareForUpdate(obj, old runtime.Object) {
	newPod := obj.(*api.Pod)
	oldPod := old.(*api.Pod)
	newPod.Status = oldPod.Status
}

// Validate validates a new pod.
func (podStrategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList {
	pod := obj.(*api.Pod)
	return validation.ValidatePod(pod)
}

// AllowCreateOnUpdate is false for pods.
func (podStrategy) AllowCreateOnUpdate() bool {
	return false
}

// ValidateUpdate is the default update validation for an end user.
func (podStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
	errorList := validation.ValidatePod(obj.(*api.Pod))
	return append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...)
}

// CheckGracefulDelete allows a pod to be gracefully deleted.
func (podStrategy) CheckGracefulDelete(obj runtime.Object, options *api.DeleteOptions) bool {
	return false
}

type podStatusStrategy struct {
	podStrategy
}

var StatusStrategy = podStatusStrategy{Strategy}

func (podStatusStrategy) PrepareForUpdate(obj, old runtime.Object) {
	newPod := obj.(*api.Pod)
	oldPod := old.(*api.Pod)
	newPod.Spec = oldPod.Spec
}

func (podStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
	// TODO: merge valid fields after update
	return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod))
}

// MatchPod returns a generic matcher for a given label and field selector.
func MatchPod(label labels.Selector, field fields.Selector) generic.Matcher {
	return &generic.SelectionPredicate{
		Label: label,
		Field: field,
		GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
			pod, ok := obj.(*api.Pod)
			if !ok {
				return nil, nil, fmt.Errorf("not a pod")
			}
			return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), nil
		},
	}
}

// PodToSelectableFields returns a label set that represents the object
// TODO: fields are not labels, and the validation rules for them do not apply.
func PodToSelectableFields(pod *api.Pod) fields.Set {
	return fields.Set{
		"metadata.name": pod.Name,
		"spec.host":     pod.Spec.Host,
		"status.phase":  string(pod.Status.Phase),
	}
}

// ResourceGetter is an interface for retrieving resources by ResourceLocation.
type ResourceGetter interface {
	Get(api.Context, string) (runtime.Object, error)
}

func getPod(getter ResourceGetter, ctx api.Context, name string) (*api.Pod, error) {
	obj, err := getter.Get(ctx, name)
	if err != nil {
		return nil, err
	}
	pod := obj.(*api.Pod)
	if pod == nil {
		return nil, fmt.Errorf("Unexpected object type: %#v", pod)
	}
	return pod, nil
}

// ResourceLocation returns a URL to which one can send traffic for the specified pod.
func ResourceLocation(getter ResourceGetter, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) {
	// Allow ID as "podname" or "podname:port".  If port is not specified,
	// try to use the first defined port on the pod.
	parts := strings.Split(id, ":")
	if len(parts) > 2 {
		return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id))
	}
	name := parts[0]
	port := ""
	if len(parts) == 2 {
		// TODO: if port is not a number but a "(container)/(portname)", do a name lookup.
		port = parts[1]
	}

	pod, err := getPod(getter, ctx, name)
	if err != nil {
		return nil, nil, err
	}

	// Try to figure out a port.
	if port == "" {
		for i := range pod.Spec.Containers {
			if len(pod.Spec.Containers[i].Ports) > 0 {
				port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort)
				break
			}
		}
	}

	// We leave off the scheme ('http://') because we have no idea what sort of server
	// is listening at this endpoint.
	loc := &url.URL{}
	if port == "" {
		loc.Host = pod.Status.PodIP
	} else {
		loc.Host = net.JoinHostPort(pod.Status.PodIP, port)
	}
	return loc, nil, nil
}

// LogLocation returns a the log URL for a pod container. If opts.Container is blank
// and only one container is present in the pod, that container is used.
func LogLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string, opts *api.PodLogOptions) (*url.URL, http.RoundTripper, error) {

	pod, err := getPod(getter, ctx, name)
	if err != nil {
		return nil, nil, err
	}

	// Try to figure out a container
	container := opts.Container
	if container == "" {
		if len(pod.Spec.Containers) == 1 {
			container = pod.Spec.Containers[0].Name
		} else {
			return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
		}
	}
	nodeHost := pod.Status.HostIP
	if len(nodeHost) == 0 {
		// If pod has not been assigned a host, return an empty location
		return nil, nil, nil
	}
	nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost)
	if err != nil {
		return nil, nil, err
	}
	params := url.Values{}
	if opts.Follow {
		params.Add("follow", "true")
	}
	loc := &url.URL{
		Scheme:   nodeScheme,
		Host:     fmt.Sprintf("%s:%d", nodeHost, nodePort),
		Path:     fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, name, container),
		RawQuery: params.Encode(),
	}
	return loc, nodeTransport, nil
}

// ExecLocation returns the exec URL for a pod container. If opts.Container is blank
// and only one container is present in the pod, that container is used.
func ExecLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string, opts *api.PodExecOptions) (*url.URL, http.RoundTripper, error) {

	pod, err := getPod(getter, ctx, name)
	if err != nil {
		return nil, nil, err
	}

	// Try to figure out a container
	container := opts.Container
	if container == "" {
		if len(pod.Spec.Containers) == 1 {
			container = pod.Spec.Containers[0].Name
		} else {
			return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
		}
	}
	nodeHost := pod.Status.HostIP
	if len(nodeHost) == 0 {
		// If pod has not been assigned a host, return an empty location
		return nil, nil, fmt.Errorf("pod %s does not have a host assigned", name)
	}
	nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost)
	if err != nil {
		return nil, nil, err
	}
	params := url.Values{}
	if opts.Stdin {
		params.Add(api.ExecStdinParam, "1")
	}
	if opts.Stdout {
		params.Add(api.ExecStdoutParam, "1")
	}
	if opts.Stderr {
		params.Add(api.ExecStderrParam, "1")
	}
	if opts.TTY {
		params.Add(api.ExecTTYParam, "1")
	}
	params.Add("command", opts.Command)
	loc := &url.URL{
		Scheme:   nodeScheme,
		Host:     fmt.Sprintf("%s:%d", nodeHost, nodePort),
		Path:     fmt.Sprintf("/exec/%s/%s/%s", pod.Namespace, name, container),
		RawQuery: params.Encode(),
	}
	return loc, nodeTransport, nil
}

// PortForwardLocation returns a the port-forward URL for a pod.
func PortForwardLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string) (*url.URL, http.RoundTripper, error) {

	pod, err := getPod(getter, ctx, name)
	if err != nil {
		return nil, nil, err
	}

	nodeHost := pod.Status.HostIP
	if len(nodeHost) == 0 {
		// If pod has not been assigned a host, return an empty location
		return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
	}
	nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost)
	if err != nil {
		return nil, nil, err
	}
	loc := &url.URL{
		Scheme: nodeScheme,
		Host:   fmt.Sprintf("%s:%d", nodeHost, nodePort),
		Path:   fmt.Sprintf("/portForward/%s/%s", pod.Namespace, name),
	}
	return loc, nodeTransport, nil
}
<commit_msg>Use Pod.Spec.Host instead of Pod.Status.HostIP for pod subresources<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 pod

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strings"

	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/validation"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/generic"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util/fielderrors"
)

// podStrategy implements behavior for Pods
// TODO: move to a pod specific package.
type podStrategy struct {
	runtime.ObjectTyper
	api.NameGenerator
}

// Strategy is the default logic that applies when creating and updating Pod
// objects via the REST API.
var Strategy = podStrategy{api.Scheme, api.SimpleNameGenerator}

// NamespaceScoped is true for pods.
func (podStrategy) NamespaceScoped() bool {
	return true
}

// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (podStrategy) PrepareForCreate(obj runtime.Object) {
	pod := obj.(*api.Pod)
	pod.Status = api.PodStatus{
		Phase: api.PodPending,
	}
}

// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (podStrategy) PrepareForUpdate(obj, old runtime.Object) {
	newPod := obj.(*api.Pod)
	oldPod := old.(*api.Pod)
	newPod.Status = oldPod.Status
}

// Validate validates a new pod.
func (podStrategy) Validate(ctx api.Context, obj runtime.Object) fielderrors.ValidationErrorList {
	pod := obj.(*api.Pod)
	return validation.ValidatePod(pod)
}

// AllowCreateOnUpdate is false for pods.
func (podStrategy) AllowCreateOnUpdate() bool {
	return false
}

// ValidateUpdate is the default update validation for an end user.
func (podStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
	errorList := validation.ValidatePod(obj.(*api.Pod))
	return append(errorList, validation.ValidatePodUpdate(obj.(*api.Pod), old.(*api.Pod))...)
}

// CheckGracefulDelete allows a pod to be gracefully deleted.
func (podStrategy) CheckGracefulDelete(obj runtime.Object, options *api.DeleteOptions) bool {
	return false
}

type podStatusStrategy struct {
	podStrategy
}

var StatusStrategy = podStatusStrategy{Strategy}

func (podStatusStrategy) PrepareForUpdate(obj, old runtime.Object) {
	newPod := obj.(*api.Pod)
	oldPod := old.(*api.Pod)
	newPod.Spec = oldPod.Spec
}

func (podStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) fielderrors.ValidationErrorList {
	// TODO: merge valid fields after update
	return validation.ValidatePodStatusUpdate(obj.(*api.Pod), old.(*api.Pod))
}

// MatchPod returns a generic matcher for a given label and field selector.
func MatchPod(label labels.Selector, field fields.Selector) generic.Matcher {
	return &generic.SelectionPredicate{
		Label: label,
		Field: field,
		GetAttrs: func(obj runtime.Object) (labels.Set, fields.Set, error) {
			pod, ok := obj.(*api.Pod)
			if !ok {
				return nil, nil, fmt.Errorf("not a pod")
			}
			return labels.Set(pod.ObjectMeta.Labels), PodToSelectableFields(pod), nil
		},
	}
}

// PodToSelectableFields returns a label set that represents the object
// TODO: fields are not labels, and the validation rules for them do not apply.
func PodToSelectableFields(pod *api.Pod) fields.Set {
	return fields.Set{
		"metadata.name": pod.Name,
		"spec.host":     pod.Spec.Host,
		"status.phase":  string(pod.Status.Phase),
	}
}

// ResourceGetter is an interface for retrieving resources by ResourceLocation.
type ResourceGetter interface {
	Get(api.Context, string) (runtime.Object, error)
}

func getPod(getter ResourceGetter, ctx api.Context, name string) (*api.Pod, error) {
	obj, err := getter.Get(ctx, name)
	if err != nil {
		return nil, err
	}
	pod := obj.(*api.Pod)
	if pod == nil {
		return nil, fmt.Errorf("Unexpected object type: %#v", pod)
	}
	return pod, nil
}

// ResourceLocation returns a URL to which one can send traffic for the specified pod.
func ResourceLocation(getter ResourceGetter, ctx api.Context, id string) (*url.URL, http.RoundTripper, error) {
	// Allow ID as "podname" or "podname:port".  If port is not specified,
	// try to use the first defined port on the pod.
	parts := strings.Split(id, ":")
	if len(parts) > 2 {
		return nil, nil, errors.NewBadRequest(fmt.Sprintf("invalid pod request %q", id))
	}
	name := parts[0]
	port := ""
	if len(parts) == 2 {
		// TODO: if port is not a number but a "(container)/(portname)", do a name lookup.
		port = parts[1]
	}

	pod, err := getPod(getter, ctx, name)
	if err != nil {
		return nil, nil, err
	}

	// Try to figure out a port.
	if port == "" {
		for i := range pod.Spec.Containers {
			if len(pod.Spec.Containers[i].Ports) > 0 {
				port = fmt.Sprintf("%d", pod.Spec.Containers[i].Ports[0].ContainerPort)
				break
			}
		}
	}

	// We leave off the scheme ('http://') because we have no idea what sort of server
	// is listening at this endpoint.
	loc := &url.URL{}
	if port == "" {
		loc.Host = pod.Status.PodIP
	} else {
		loc.Host = net.JoinHostPort(pod.Status.PodIP, port)
	}
	return loc, nil, nil
}

// LogLocation returns a the log URL for a pod container. If opts.Container is blank
// and only one container is present in the pod, that container is used.
func LogLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string, opts *api.PodLogOptions) (*url.URL, http.RoundTripper, error) {

	pod, err := getPod(getter, ctx, name)
	if err != nil {
		return nil, nil, err
	}

	// Try to figure out a container
	container := opts.Container
	if container == "" {
		if len(pod.Spec.Containers) == 1 {
			container = pod.Spec.Containers[0].Name
		} else {
			return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
		}
	}
	nodeHost := pod.Spec.Host
	if len(nodeHost) == 0 {
		// If pod has not been assigned a host, return an empty location
		return nil, nil, nil
	}
	nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost)
	if err != nil {
		return nil, nil, err
	}
	params := url.Values{}
	if opts.Follow {
		params.Add("follow", "true")
	}
	loc := &url.URL{
		Scheme:   nodeScheme,
		Host:     fmt.Sprintf("%s:%d", nodeHost, nodePort),
		Path:     fmt.Sprintf("/containerLogs/%s/%s/%s", pod.Namespace, name, container),
		RawQuery: params.Encode(),
	}
	return loc, nodeTransport, nil
}

// ExecLocation returns the exec URL for a pod container. If opts.Container is blank
// and only one container is present in the pod, that container is used.
func ExecLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string, opts *api.PodExecOptions) (*url.URL, http.RoundTripper, error) {

	pod, err := getPod(getter, ctx, name)
	if err != nil {
		return nil, nil, err
	}

	// Try to figure out a container
	container := opts.Container
	if container == "" {
		if len(pod.Spec.Containers) == 1 {
			container = pod.Spec.Containers[0].Name
		} else {
			return nil, nil, errors.NewBadRequest(fmt.Sprintf("a container name must be specified for pod %s", name))
		}
	}
	nodeHost := pod.Spec.Host
	if len(nodeHost) == 0 {
		// If pod has not been assigned a host, return an empty location
		return nil, nil, fmt.Errorf("pod %s does not have a host assigned", name)
	}
	nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost)
	if err != nil {
		return nil, nil, err
	}
	params := url.Values{}
	if opts.Stdin {
		params.Add(api.ExecStdinParam, "1")
	}
	if opts.Stdout {
		params.Add(api.ExecStdoutParam, "1")
	}
	if opts.Stderr {
		params.Add(api.ExecStderrParam, "1")
	}
	if opts.TTY {
		params.Add(api.ExecTTYParam, "1")
	}
	params.Add("command", opts.Command)
	loc := &url.URL{
		Scheme:   nodeScheme,
		Host:     fmt.Sprintf("%s:%d", nodeHost, nodePort),
		Path:     fmt.Sprintf("/exec/%s/%s/%s", pod.Namespace, name, container),
		RawQuery: params.Encode(),
	}
	return loc, nodeTransport, nil
}

// PortForwardLocation returns a the port-forward URL for a pod.
func PortForwardLocation(getter ResourceGetter, connInfo client.ConnectionInfoGetter, ctx api.Context, name string) (*url.URL, http.RoundTripper, error) {

	pod, err := getPod(getter, ctx, name)
	if err != nil {
		return nil, nil, err
	}

	nodeHost := pod.Spec.Host
	if len(nodeHost) == 0 {
		// If pod has not been assigned a host, return an empty location
		return nil, nil, errors.NewBadRequest(fmt.Sprintf("pod %s does not have a host assigned", name))
	}
	nodeScheme, nodePort, nodeTransport, err := connInfo.GetConnectionInfo(nodeHost)
	if err != nil {
		return nil, nil, err
	}
	loc := &url.URL{
		Scheme: nodeScheme,
		Host:   fmt.Sprintf("%s:%d", nodeHost, nodePort),
		Path:   fmt.Sprintf("/portForward/%s/%s", pod.Namespace, name),
	}
	return loc, nodeTransport, nil
}
<|endoftext|>


<commit_before>/*
Copyright 2020 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 release

import (
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/github"
	"k8s.io/release/pkg/license"
	"k8s.io/release/pkg/object"
	"k8s.io/release/pkg/spdx"
	"sigs.k8s.io/release-utils/tar"
)

// PrepareWorkspaceStage sets up the workspace by cloning a new copy of k/k.
func PrepareWorkspaceStage(directory string) error {
	logrus.Infof("Preparing workspace for staging in %s", directory)
	logrus.Infof("Cloning repository to %s", directory)
	repo, err := git.CloneOrOpenGitHubRepo(
		directory, git.DefaultGithubOrg, git.DefaultGithubRepo, false,
	)
	if err != nil {
		return errors.Wrap(err, "clone k/k repository")
	}

	token, ok := os.LookupEnv(github.TokenEnvKey)
	if !ok {
		return errors.Errorf("%s env variable is not set", github.TokenEnvKey)
	}

	if err := repo.SetURL(git.DefaultRemote, (&url.URL{
		Scheme: "https",
		User:   url.UserPassword("git", token),
		Host:   "github.com",
		Path:   filepath.Join(git.DefaultGithubOrg, git.DefaultGithubRepo),
	}).String()); err != nil {
		return errors.Wrap(err, "changing git remote of repository")
	}

	// Prewarm the SPDX licenses cache. As it is one of the main
	// remote operations, we do it now to have the data and fail early
	// is something goes wrong.
	s := spdx.NewSPDX()
	logrus.Infof("Caching SPDX license set to %s", s.Options().LicenseCacheDir)
	doptions := license.DefaultDownloaderOpts
	doptions.CacheDir = s.Options().LicenseCacheDir
	downloader, err := license.NewDownloaderWithOptions(doptions)
	if err != nil {
		return errors.Wrap(err, "creating license downloader")
	}
	// Fetch the SPDX licenses
	if _, err := downloader.GetLicenses(); err != nil {
		return errors.Wrap(err, "retrieving SPDX licenses")
	}

	return nil
}

// PrepareWorkspaceRelease sets up the workspace by downloading and extracting
// the staged sources on the provided bucket.
func PrepareWorkspaceRelease(directory, buildVersion, bucket string) error {
	logrus.Infof("Preparing workspace for release in %s", directory)
	logrus.Infof("Searching for staged %s on %s", SourcesTar, bucket)
	tempDir, err := os.MkdirTemp("", "staged-")
	if err != nil {
		return errors.Wrap(err, "create staged sources temp dir")
	}
	defer os.RemoveAll(tempDir)

	// On `release`, we lookup the staged sources and use them directly
	src := filepath.Join(bucket, StagePath, buildVersion, SourcesTar)
	dst := filepath.Join(tempDir, SourcesTar)

	gcs := object.NewGCS()
	gcs.WithAllowMissing(false)
	if err := gcs.CopyToLocal(src, dst); err != nil {
		return errors.Wrap(err, "copying staged sources from GCS")
	}

	logrus.Info("Got staged sources, extracting archive")
	if err := tar.Extract(
		dst, strings.TrimSuffix(directory, "/src/k8s.io/kubernetes"),
	); err != nil {
		return errors.Wrapf(err, "extracting %s", dst)
	}

	return nil
}
<commit_msg>Reseed the staged clone of k/k<commit_after>/*
Copyright 2020 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 release

import (
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	"k8s.io/release/pkg/git"
	"k8s.io/release/pkg/github"
	"k8s.io/release/pkg/license"
	"k8s.io/release/pkg/object"
	"k8s.io/release/pkg/spdx"
	"sigs.k8s.io/release-utils/tar"
)

// PrepareWorkspaceStage sets up the workspace by cloning a new copy of k/k.
func PrepareWorkspaceStage(directory string) error {
	logrus.Infof("Preparing workspace for staging in %s", directory)
	logrus.Infof("Cloning repository to %s", directory)
	repo, err := git.CloneOrOpenGitHubRepo(
		directory, git.DefaultGithubOrg, git.DefaultGithubRepo, false,
	)
	if err != nil {
		return errors.Wrap(err, "clone k/k repository")
	}

	token, ok := os.LookupEnv(github.TokenEnvKey)
	if !ok {
		return errors.Errorf("%s env variable is not set", github.TokenEnvKey)
	}

	if err := repo.SetURL(git.DefaultRemote, (&url.URL{
		Scheme: "https",
		User:   url.UserPassword("git", token),
		Host:   "github.com",
		Path:   filepath.Join(git.DefaultGithubOrg, git.DefaultGithubRepo),
	}).String()); err != nil {
		return errors.Wrap(err, "changing git remote of repository")
	}

	// Prewarm the SPDX licenses cache. As it is one of the main
	// remote operations, we do it now to have the data and fail early
	// is something goes wrong.
	s := spdx.NewSPDX()
	logrus.Infof("Caching SPDX license set to %s", s.Options().LicenseCacheDir)
	doptions := license.DefaultDownloaderOpts
	doptions.CacheDir = s.Options().LicenseCacheDir
	downloader, err := license.NewDownloaderWithOptions(doptions)
	if err != nil {
		return errors.Wrap(err, "creating license downloader")
	}
	// Fetch the SPDX licenses
	if _, err := downloader.GetLicenses(); err != nil {
		return errors.Wrap(err, "retrieving SPDX licenses")
	}

	return nil
}

// PrepareWorkspaceRelease sets up the workspace by downloading and extracting
// the staged sources on the provided bucket.
func PrepareWorkspaceRelease(directory, buildVersion, bucket string) error {
	logrus.Infof("Preparing workspace for release in %s", directory)
	logrus.Infof("Searching for staged %s on %s", SourcesTar, bucket)
	tempDir, err := os.MkdirTemp("", "staged-")
	if err != nil {
		return errors.Wrap(err, "create staged sources temp dir")
	}
	defer os.RemoveAll(tempDir)

	// On `release`, we lookup the staged sources and use them directly
	src := filepath.Join(bucket, StagePath, buildVersion, SourcesTar)
	dst := filepath.Join(tempDir, SourcesTar)

	gcs := object.NewGCS()
	gcs.WithAllowMissing(false)
	if err := gcs.CopyToLocal(src, dst); err != nil {
		return errors.Wrap(err, "copying staged sources from GCS")
	}

	logrus.Info("Got staged sources, extracting archive")
	if err := tar.Extract(
		dst, strings.TrimSuffix(directory, "/src/k8s.io/kubernetes"),
	); err != nil {
		return errors.Wrapf(err, "extracting %s", dst)
	}

	// Reset the github token in the staged k/k clone
	token, ok := os.LookupEnv(github.TokenEnvKey)
	if !ok {
		return errors.Errorf("%s env variable is not set", github.TokenEnvKey)
	}

	repo, err := git.OpenRepo(directory)
	if err != nil {
		return errors.Wrap(err, "opening staged clone of k/k")
	}

	if err := repo.SetURL(git.DefaultRemote, (&url.URL{
		Scheme: "https",
		User:   url.UserPassword("git", token),
		Host:   "github.com",
		Path:   filepath.Join(git.DefaultGithubOrg, git.DefaultGithubRepo),
	}).String()); err != nil {
		return errors.Wrap(err, "changing git remote of repository")
	}

	return nil
}
<|endoftext|>