<commit_before>package endly_test

import (
	"github.com/stretchr/testify/assert"
	"github.com/viant/endly"
	"github.com/viant/toolbox/url"
	"testing"
	"fmt"
)

func TestVc_Status(t *testing.T) {
	credentialFile, err := GetDummyCredential()
	assert.Nil(t, err)
	var target = url.NewResource("ssh://127.0.0.1/Projects/project1/trunk", credentialFile) //
	target.Type = "svn"
	var manager = endly.NewManager()
	var useCases = []struct {
		baseDir  string
		Request  *endly.VcStatusRequest
		Expected *endly.VcInfo
		Error    string
	}{
		{
			"test/vc/svn/status/darwin",
			&endly.VcStatusRequest{
				Target:     target,
			},
			&endly.VcInfo{
				Untracked:[]string{".idea"},
				New:[]string{"test.dep"},
				Modified:[]string{"pom.xml", "src/main/java/com/viant/Dummy.java"},
				Deleted:[]string{"src/test/java/com/viant/DummyTest.java"},
				IsVersionControlManaged:true,
				Origin:"http://svn.viant.com/svn/projects/project1/trunk",
				Branch:"trunk",
			},

			"",
		},
	}

	for _, useCase := range useCases {
		var target = useCase.Request.Target
		execService, err := GetReplayService(useCase.baseDir)
		if assert.Nil(t, err) {
			context, err := OpenTestContext(manager, target, execService)
			service, err := context.Service(endly.VersionControlServiceID)
			assert.Nil(t, err)

			defer context.Close()
			if assert.Nil(t, err) {
				serviceResponse := service.Run(context, useCase.Request)

				var baseCase = useCase.baseDir + " "
				assert.Equal(t, useCase.Error, serviceResponse.Error, baseCase)

				actual, ok := serviceResponse.Response.(*endly.VcInfo)
				if !ok {
					assert.Fail(t, fmt.Sprintf("process serviceResponse was empty %v %T", baseCase, serviceResponse.Response))
					continue
				}

				if actual == nil {
					continue
				}
				var expected = useCase.Expected
				assert.Equal(t, expected.Untracked, actual.Untracked, "Untracked "+baseCase)
				assert.Equal(t, expected.New, actual.New, "New "+baseCase)
				assert.Equal(t, expected.Modified, actual.Modified, "Modified "+baseCase)
				assert.Equal(t, expected.Deleted, actual.Deleted, "Deleted "+baseCase)
				assert.Equal(t, expected.IsVersionControlManaged, actual.IsVersionControlManaged, "IsVersionControlManaged "+baseCase)
				assert.Equal(t, expected.IsUptoDate, actual.IsUptoDate, "IsUptoDate "+baseCase)
				assert.Equal(t, expected.Origin, actual.Origin, "Origin "+baseCase)
				assert.Equal(t, expected.Branch, actual.Branch, "Branch "+baseCase)

			}
		}
	}
}

func TestVc_Checkout(t *testing.T) {
	credentialFile, err := GetDummyCredential()
	assert.Nil(t, err)
	var target = url.NewResource("ssh://127.0.0.1/Projects/project1/trunk", credentialFile) //
	target.Type = "svn"
	var manager = endly.NewManager()
	var useCases = []struct {
		baseDir  string
		Request  *endly.VcCheckoutRequest
		Expected *endly.VcCheckoutResponse
		Error    string
	}{
		{
			"test/vc/svn/checkout/error/darwin",
			&endly.VcCheckoutRequest{
				Target:     url.NewResource("scp://127.0.0.1:22/tmp/project2/trunk", credentialFile),
				Origin:url.NewResource("http://svn.viant.com/svn/projects/project1/trunk", credentialFile),

			},
			&endly.VcCheckoutResponse{

				//Untracked:[]string{".idea"},
				//New:[]string{"test.dep"},
				//Modified:[]string{"pom.xml", "src/main/java/com/viant/Dummy.java"},
				//Deleted:[]string{"src/test/java/com/viant/DummyTest.java"},
				//IsVersionControlManaged:true,
				//Origin:"http://svn.viant.com/svn/projects/project1/trunk",
				//Branch:"trunk",
			},

			"failed to checkout version: http://svn.viant.com/svn/projects/project1/trunk -> scp://127.0.0.1:22/tmp/project2/trunk, failed to authenticate username: awitas with dummy9.json secret",
		},
		{
			"test/vc/svn/checkout/new/darwin",
			&endly.VcCheckoutRequest{
				Target:     url.NewResource("scp://127.0.0.1:22/tmp/project1/trunk", credentialFile),
				Origin:url.NewResource("http://svn.viant.com/svn/projects/project1/trunk", credentialFile),

			},
			&endly.VcCheckoutResponse{
				Checkouts:map[string]*endly.VcInfo{
					"http://svn.viant.com/svn/projects/project1/trunk":&endly.VcInfo{
						Origin:"http://svn.viant.com/svn/projects/project1/trunk",
						IsVersionControlManaged:true,
						IsUptoDate:true,
						Modified:[]string{},
						Branch:"trunk",
					},
				},
			},
			"",
		},
		{
			"test/vc/svn/checkout/existing/darwin",
			&endly.VcCheckoutRequest{
				Target:     url.NewResource("scp://127.0.0.1:22/tmp/project1/trunk", credentialFile),
				Origin:url.NewResource("http://svn.viant.com/svn/projects/project1/trunk", credentialFile),

			},
			&endly.VcCheckoutResponse{
				Checkouts:map[string]*endly.VcInfo{
					"http://svn.viant.com/svn/projects/project1/trunk":&endly.VcInfo{
						Origin:"http://svn.viant.com/svn/projects/project1/trunk",
						IsVersionControlManaged:true,
						IsUptoDate:false,
						Branch:"trunk",
						Modified:[]string{"pom.xml"},
					},
				},
			},
			"",
		},
		{
			"test/vc/svn/checkout/modules/darwin",
			&endly.VcCheckoutRequest{
				Target:     url.NewResource("scp://127.0.0.1:22/tmp/project3/", credentialFile),
				Origin:url.NewResource("http://svn.viant.com/svn/projects/", credentialFile),
				Modules:[]string{"project1/trunk", "project2/trunk"},
			},
			&endly.VcCheckoutResponse{
				Checkouts:map[string]*endly.VcInfo{
					"http://svn.viant.com/svn/projects/project1/trunk":&endly.VcInfo{
						Origin:"http://svn.viant.com/svn/projects/project1/trunk",
						IsVersionControlManaged:true,
						IsUptoDate:true,
						Branch:"trunk",
						Modified:[]string{},
					},
					"http://svn.viant.com/svn/projects/project2/trunk":&endly.VcInfo{
						Origin:"http://svn.viant.com/svn/projects/project2/trunk",
						IsVersionControlManaged:true,
						IsUptoDate:true,
						Branch:"trunk",
						Modified:[]string{},
					},

				},
			},
			"",
		},
	}

	for _, useCase := range useCases {
		var target = useCase.Request.Target
		execService, err := GetReplayService(useCase.baseDir)
		if assert.Nil(t, err) {
			context, err := OpenTestContext(manager, target, execService)
			service, err := context.Service(endly.VersionControlServiceID)
			assert.Nil(t, err)

			defer context.Close()
			if assert.Nil(t, err) {
				serviceResponse := service.Run(context, useCase.Request)

				var baseCase = useCase.baseDir + " "
				assert.Equal(t, useCase.Error, serviceResponse.Error, baseCase)

				response, ok := serviceResponse.Response.(*endly.VcCheckoutResponse)
				if !ok {
					assert.Fail(t, fmt.Sprintf("process serviceResponse was empty %v %T", baseCase, serviceResponse.Response))
					continue
				}

				if response == nil {
					continue
				}
				assert.EqualValues(t, useCase.Error, serviceResponse.Error)

				if useCase.Error != "" {
					continue
				}
				for key, actual := range response.Checkouts {

					expected, ok := useCase.Expected.Checkouts[key]
					if ! assert.True(t, ok, "missing origin: "+ key) {
						continue
					}
					assert.Equal(t, expected.IsVersionControlManaged, actual.IsVersionControlManaged, "IsVersionControlManaged "+baseCase)
					assert.Equal(t, expected.IsUptoDate, actual.IsUptoDate, "IsUptoDate "+baseCase)
					assert.Equal(t, expected.Origin, actual.Origin, "Origin "+baseCase)
					assert.Equal(t, expected.Branch, actual.Branch, "Branch "+baseCase)
					assert.Equal(t, expected.Modified, actual.Modified, "Modified "+baseCase)
				}

			}
		}
	}
}

//
//func TestService_Run2StatusRequest(t *testing.T) {
//
//
//	var credentialFile = path.Join(os.Getenv("HOME"), ".secret/scp.json")
//	var svnCredentialFile = path.Join(os.Getenv("HOME"), ".secret/vsvn.json")
//
//
//	//var target = url.NewResource("scp://35.197.115.53:22/", credentialFile) //
//	var target = url.NewResource("scp://127.0.0.1:22/tmp/project3/", credentialFile) //
//	manager := endly.NewManager()
//
//	context, err := OpenTestRecorderContext(manager, target, "test/vc/svn/checkout/modules/darwin")
//
//	defer context.Close()
//
//	service, err := manager.Service(endly.VersionControlServiceID)
//	assert.Nil(t, err)
//	assert.NotNil(t, service)
//	var origin = url.NewResource("http://svn.viant.com/svn/projects/", svnCredentialFile)
//	origin.Type = "svn"
//
//	response := service.Run(context, &endly.VcCheckoutRequest{
//		Target: target,
//		Origin:origin,
//		Modules:[]string{"project1/trunk", "project2/trunk"},
//		RemoveLocalChanges:true,
//	})
//	assert.NotNil(t, response)
//	assert.Equal(t, "", response.Error)
//	info, ok := response.Response.(*endly.VcInfo)
//	assert.True(t, ok)
//	assert.NotNil(t, info)
//
//	//assert.Equal(t, "master", info.Branch)
//	//assert.Equal(t, "3d764da443b3852260666d2c527872e2629e40e2", info.Revision)
//	//assert.False(t, info.IsUptoDate)
//	//assert.True(t, info.HasPendingChanges())
//
//}

//
//
//
////
////func TestService_RunCheckout(t *testing.T) {
////	fileName, _, _ := toolbox.CallerInfo(2)
////	parent, _ := path.Split(fileName)
////
////	testProject1 := fmt.Sprintf("%vtest/vc/project1", parent)
////	testProject2 := fmt.Sprintf("%vtest/vc/project2", parent)
////	command := exec.Command("/bin/cp", "-rf", testProject1, testProject2)
////	_, err := command.CombinedOutput()
////	assert.Nil(t, err)
////
////	manager := endly.NewManager()
////	service, err := manager.Service(endly.VersionControlServiceID)
////	assert.Nil(t, err)
////
////	context := manager.NewContext(toolbox.NewContext())
////	response := service.Run(context, &endly.VcCheckoutRequest{
////		Origin: &url.Resource{
////			URL: "https://github.com/adranwit/p",
////		},
////		Target: &url.Resource{
////			URL:  "scp://" + testProject2,
////			Type: "git",
////		},
////	})
////	assert.Equal(t, "", response.Error)
////
////}
<commit_msg>updated unit test for version control<commit_after>package endly_test

import (
	"github.com/stretchr/testify/assert"
	"github.com/viant/endly"
	"github.com/viant/toolbox/url"
	"testing"
	"fmt"
	"strings"
)

func TestVc_Status(t *testing.T) {
	credentialFile, err := GetDummyCredential()
	assert.Nil(t, err)
	var target = url.NewResource("ssh://127.0.0.1/Projects/project1/trunk", credentialFile) //
	target.Type = "svn"
	var manager = endly.NewManager()
	var useCases = []struct {
		baseDir  string
		Request  *endly.VcStatusRequest
		Expected *endly.VcInfo
		Error    string
	}{
		{
			"test/vc/svn/status/darwin",
			&endly.VcStatusRequest{
				Target: target,
			},
			&endly.VcInfo{
				Untracked:               []string{".idea"},
				New:                     []string{"test.dep"},
				Modified:                []string{"pom.xml", "src/main/java/com/viant/Dummy.java"},
				Deleted:                 []string{"src/test/java/com/viant/DummyTest.java"},
				IsVersionControlManaged: true,
				Origin:                  "http://svn.viant.com/svn/projects/project1/trunk",
				Branch:                  "trunk",
			},

			"",
		},
	}

	for _, useCase := range useCases {
		var target = useCase.Request.Target
		execService, err := GetReplayService(useCase.baseDir)
		if assert.Nil(t, err) {
			context, err := OpenTestContext(manager, target, execService)
			service, err := context.Service(endly.VersionControlServiceID)
			assert.Nil(t, err)

			defer context.Close()
			if assert.Nil(t, err) {
				serviceResponse := service.Run(context, useCase.Request)

				var baseCase = useCase.baseDir + " "
				assert.Equal(t, useCase.Error, serviceResponse.Error, baseCase)

				actual, ok := serviceResponse.Response.(*endly.VcInfo)
				if !ok {
					assert.Fail(t, fmt.Sprintf("process serviceResponse was empty %v %T", baseCase, serviceResponse.Response))
					continue
				}

				if actual == nil {
					continue
				}
				var expected = useCase.Expected
				assert.Equal(t, expected.Untracked, actual.Untracked, "Untracked "+baseCase)
				assert.Equal(t, expected.New, actual.New, "New "+baseCase)
				assert.Equal(t, expected.Modified, actual.Modified, "Modified "+baseCase)
				assert.Equal(t, expected.Deleted, actual.Deleted, "Deleted "+baseCase)
				assert.Equal(t, expected.IsVersionControlManaged, actual.IsVersionControlManaged, "IsVersionControlManaged "+baseCase)
				assert.Equal(t, expected.IsUptoDate, actual.IsUptoDate, "IsUptoDate "+baseCase)
				assert.Equal(t, expected.Origin, actual.Origin, "Origin "+baseCase)
				assert.Equal(t, expected.Branch, actual.Branch, "Branch "+baseCase)

			}
		}
	}
}

func TestVc_Checkout(t *testing.T) {
	credentialFile, err := GetDummyCredential()
	gitCredentialFile, err := GetCredential("git.json","adrianwit", "***")
	assert.Nil(t, err)
	var target = url.NewResource("ssh://127.0.0.1/Projects/project1/trunk", credentialFile) //
	target.Type = "svn"
	var manager = endly.NewManager()
	var useCases = []struct {
		baseDir  string
		Request  *endly.VcCheckoutRequest
		Expected *endly.VcCheckoutResponse
		Error    string
	}{
		{
			"test/vc/svn/checkout/error/darwin",
			&endly.VcCheckoutRequest{
				Target: url.NewResource("scp://127.0.0.1:22/tmp/project2/trunk", credentialFile),
				Origin: url.NewResource("http://svn.viant.com/svn/projects/project1/trunk", credentialFile),
			},
			&endly.VcCheckoutResponse{
			},
			"failed to checkout version: http://svn.viant.com/svn/projects/project1/trunk -> scp://127.0.0.1:22/tmp/project2/trunk, failed to authenticate username: awitas with",
		},
		{
			"test/vc/svn/checkout/new/darwin",
			&endly.VcCheckoutRequest{
				Target: url.NewResource("scp://127.0.0.1:22/tmp/project1/trunk", credentialFile),
				Origin: url.NewResource("http://svn.viant.com/svn/projects/project1/trunk", credentialFile),
			},
			&endly.VcCheckoutResponse{
				Checkouts: map[string]*endly.VcInfo{
					"http://svn.viant.com/svn/projects/project1/trunk": &endly.VcInfo{
						Origin:                  "http://svn.viant.com/svn/projects/project1/trunk",
						IsVersionControlManaged: true,
						IsUptoDate:              true,
						Modified:                []string{},
						Branch:                  "trunk",
					},
				},
			},
			"",
		},
		{
			"test/vc/svn/checkout/existing/darwin",
			&endly.VcCheckoutRequest{
				Target: url.NewResource("scp://127.0.0.1:22/tmp/project1/trunk", credentialFile),
				Origin: url.NewResource("http://svn.viant.com/svn/projects/project1/trunk", credentialFile),
			},
			&endly.VcCheckoutResponse{
				Checkouts: map[string]*endly.VcInfo{
					"http://svn.viant.com/svn/projects/project1/trunk": &endly.VcInfo{
						Origin:                  "http://svn.viant.com/svn/projects/project1/trunk",
						IsVersionControlManaged: true,
						IsUptoDate:              false,
						Branch:                  "trunk",
						Modified:                []string{"pom.xml"},
					},
				},
			},
			"",
		},
		{
			"test/vc/svn/checkout/modules/darwin",
			&endly.VcCheckoutRequest{
				Target:  url.NewResource("scp://127.0.0.1:22/tmp/project3/", credentialFile),
				Origin:  url.NewResource("http://svn.viant.com/svn/projects/", credentialFile),
				Modules: []string{"project1/trunk", "project2/trunk"},
			},
			&endly.VcCheckoutResponse{
				Checkouts: map[string]*endly.VcInfo{
					"http://svn.viant.com/svn/projects/project1/trunk": &endly.VcInfo{
						Origin:                  "http://svn.viant.com/svn/projects/project1/trunk",
						IsVersionControlManaged: true,
						IsUptoDate:              true,
						Branch:                  "trunk",
						Modified:                []string{},
					},
					"http://svn.viant.com/svn/projects/project2/trunk": &endly.VcInfo{
						Origin:                  "http://svn.viant.com/svn/projects/project2/trunk",
						IsVersionControlManaged: true,
						IsUptoDate:              true,
						Branch:                  "trunk",
						Modified:                []string{},
					},
				},
			},
			"",
		},
		{
			"test/vc/git/checkout/private/error/linux",
			&endly.VcCheckoutRequest{
				Target:  url.NewResource("scp://127.0.0.1:22/tmp/myproj", credentialFile),
				Origin:  url.NewResource("https://github.com/adrianwit/projectA", gitCredentialFile),
			},
			&endly.VcCheckoutResponse{
			},
			"failed to checkout version: https://github.com/adrianwit/projectA -> scp://127.0.0.1:22/tmp/myproj, failed to authenticate username: adrianwit with",
		},
		{
			"test/vc/git/checkout/private/new/linux",
			&endly.VcCheckoutRequest{
				Target:  url.NewResource("scp://127.0.0.1:22/tmp/myproj", credentialFile),
				Origin:  url.NewResource("https://github.com/adrianwit/projectA", gitCredentialFile),
			},
			&endly.VcCheckoutResponse{
				Checkouts: map[string]*endly.VcInfo{
					"https://github.com/adrianwit/projectA": &endly.VcInfo{
						Origin:                  "https://adrianwit@github.com:443/adrianwit/projectA",
						IsVersionControlManaged: true,
						IsUptoDate:              true,
						Branch:                  "master",
						Modified:                []string{},
					},

				},
			},
			"",
		},
		{
			"test/vc/git/checkout/private/existing/linux",
			&endly.VcCheckoutRequest{
				Target:  url.NewResource("scp://127.0.0.1:22/tmp/myproj", credentialFile),
				Origin:  url.NewResource("https://github.com/adrianwit/projectA", gitCredentialFile),
			},
			&endly.VcCheckoutResponse{
				Checkouts: map[string]*endly.VcInfo{
					"https://github.com/adrianwit/projectA": &endly.VcInfo{
						Origin:                  "https://adrianwit@github.com:443/adrianwit/projectA",
						IsVersionControlManaged: true,
						IsUptoDate:              false,
						Branch:                  "master",
						Modified:                []string{"README.md"},
					},

				},
			},
			"",
		},

	}

	for _, useCase := range useCases {
		var target = useCase.Request.Target
		execService, err := GetReplayService(useCase.baseDir)
		if assert.Nil(t, err) {
			context, err := OpenTestContext(manager, target, execService)
			service, err := context.Service(endly.VersionControlServiceID)
			assert.Nil(t, err)

			defer context.Close()
			if assert.Nil(t, err) {
				serviceResponse := service.Run(context, useCase.Request)

				var baseCase = useCase.baseDir + " "
				if useCase.Error != "" {
					assert.True(t, strings.HasPrefix(serviceResponse.Error, useCase.Error), baseCase + " " + serviceResponse.Error)
				}
				response, ok := serviceResponse.Response.(*endly.VcCheckoutResponse)
				if !ok {
					assert.Fail(t, fmt.Sprintf("process serviceResponse was empty %v %T", baseCase, serviceResponse.Response))
					continue
				}

				if response == nil {
					continue
				}
				assert.EqualValues(t, useCase.Error, serviceResponse.Error)

				if useCase.Error != "" {
					continue
				}
				for key, actual := range response.Checkouts {

					expected, ok := useCase.Expected.Checkouts[key]
					if ! assert.True(t, ok, "missing origin: "+key) {
						continue
					}
					assert.Equal(t, expected.IsVersionControlManaged, actual.IsVersionControlManaged, "IsVersionControlManaged "+baseCase)
					 assert.Equal(t, expected.IsUptoDate, actual.IsUptoDate, "IsUptoDate "+baseCase)
					assert.Equal(t, expected.Origin, actual.Origin, "Origin "+baseCase)
					assert.Equal(t, expected.Branch, actual.Branch, "Branch "+baseCase)
					assert.Equal(t, expected.Modified, actual.Modified, "Modified "+baseCase)
				}

			}
		}
	}
}

////
//func TestService_Run2StatusRequest(t *testing.T) {
//
//
//	var credentialFile = path.Join(os.Getenv("HOME"), ".secret/scp.json")
//	var vcCredentialFile = path.Join(os.Getenv("HOME"), ".secret/scp.json")
//
//
//	//var target = url.NewResource("scp://35.197.115.53:22/", credentialFile) //
//	var target = url.NewResource("scp://35.197.16.129:22/tmp/myproj", credentialFile) //
//	manager := endly.NewManager()
//
//	context, err := OpenTestRecorderContext(manager, target, "test/vc/git/checkout/private/error/linux")
//
//	defer context.Close()
//
//
//	service, err := manager.Service(endly.VersionControlServiceID)
//	assert.Nil(t, err)
//	assert.NotNil(t, service)
//	var origin = url.NewResource("https://github.com/adrianwit/projectA", vcCredentialFile)
//
//	response := service.Run(context, &endly.VcCheckoutRequest{
//		Target: target,
//		Origin:origin,
//	})
//
//
//	assert.NotNil(t, response)
//	assert.Equal(t, "", response.Error)
//	info, ok := response.Response.(*endly.VcInfo)
//	assert.True(t, ok)
//	assert.NotNil(t, info)
//
//
//
//	//assert.Equal(t, "master", info.Branch)
//	//assert.Equal(t, "3d764da443b3852260666d2c527872e2629e40e2", info.Revision)
//	//assert.False(t, info.IsUptoDate)
//	//assert.True(t, info.HasPendingChanges())
//
//}

<|endoftext|>


<commit_before>/*
Copyright 2011 Google Inc.

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

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

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

// Package jsonconfig defines a helper type for JSON objects to be
// used for configuration.
package jsonconfig

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

// Obj is a JSON configuration map.
type Obj map[string]interface{}

// Reads json config data from the specified open file, expanding
// all expressions
func ReadFile(configPath string) (Obj, error) {
	var c ConfigParser
	return c.ReadFile(configPath)
}

func (jc Obj) RequiredObject(key string) Obj {
	return jc.obj(key, false)
}

func (jc Obj) OptionalObject(key string) Obj {
	return jc.obj(key, true)
}

func (jc Obj) obj(key string, optional bool) Obj {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if optional {
			return make(Obj)
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (object)", key))
		return make(Obj)
	}
	m, ok := ei.(map[string]interface{})
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be an object, not %T", key, ei))
		return make(Obj)
	}
	return Obj(m)
}

func (jc Obj) RequiredString(key string) string {
	return jc.string(key, nil)
}

func (jc Obj) OptionalString(key, def string) string {
	return jc.string(key, &def)
}

func (jc Obj) string(key string, def *string) string {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if def != nil {
			return *def
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (string)", key))
		return ""
	}
	s, ok := ei.(string)
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be a string", key))
		return ""
	}
	return s
}

func (jc Obj) RequiredStringOrObject(key string) interface{} {
	return jc.stringOrObject(key, true)
}

func (jc Obj) OptionalStringOrObject(key string) interface{} {
	return jc.stringOrObject(key, false)
}

func (jc Obj) stringOrObject(key string, required bool) interface{} {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if !required {
			return nil
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (string or object)", key))
		return ""
	}
	if _, ok := ei.(map[string]interface{}); ok {
		return ei
	}
	if _, ok := ei.(string); ok {
		return ei
	}
	jc.appendError(fmt.Errorf("Expected config key %q to be a string or object", key))
	return ""
}

func (jc Obj) RequiredBool(key string) bool {
	return jc.bool(key, nil)
}

func (jc Obj) OptionalBool(key string, def bool) bool {
	return jc.bool(key, &def)
}

func (jc Obj) bool(key string, def *bool) bool {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if def != nil {
			return *def
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (boolean)", key))
		return false
	}
	switch v := ei.(type) {
	case bool:
		return v
	case string:
		b, err := strconv.ParseBool(v)
		if err != nil {
			jc.appendError(fmt.Errorf("Config key %q has bad boolean format %q", key, v))
		}
		return b
	default:
		jc.appendError(fmt.Errorf("Expected config key %q to be a boolean", key))
		return false
	}
}

func (jc Obj) RequiredInt(key string) int {
	return jc.int(key, nil)
}

func (jc Obj) OptionalInt(key string, def int) int {
	return jc.int(key, &def)
}

func (jc Obj) int(key string, def *int) int {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if def != nil {
			return *def
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key))
		return 0
	}
	b, ok := ei.(float64)
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be a number", key))
		return 0
	}
	return int(b)
}

func (jc Obj) RequiredInt64(key string) int64 {
	return jc.int64(key, nil)
}

func (jc Obj) OptionalInt64(key string, def int64) int64 {
	return jc.int64(key, &def)
}

func (jc Obj) int64(key string, def *int64) int64 {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if def != nil {
			return *def
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key))
		return 0
	}
	b, ok := ei.(float64)
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be a number", key))
		return 0
	}
	return int64(b)
}

func (jc Obj) RequiredList(key string) []string {
	return jc.requiredList(key, true)
}

func (jc Obj) OptionalList(key string) []string {
	return jc.requiredList(key, false)
}

func (jc Obj) requiredList(key string, required bool) []string {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if required {
			jc.appendError(fmt.Errorf("Missing required config key %q (list of strings)", key))
		}
		return nil
	}
	eil, ok := ei.([]interface{})
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be a list, not %T", key, ei))
		return nil
	}
	sl := make([]string, len(eil))
	for i, ei := range eil {
		s, ok := ei.(string)
		if !ok {
			jc.appendError(fmt.Errorf("Expected config key %q index %d to be a string, not %T", key, i, ei))
			return nil
		}
		sl[i] = s
	}
	return sl
}

func (jc Obj) noteKnownKey(key string) {
	_, ok := jc["_knownkeys"]
	if !ok {
		jc["_knownkeys"] = make(map[string]bool)
	}
	jc["_knownkeys"].(map[string]bool)[key] = true
}

func (jc Obj) appendError(err error) {
	ei, ok := jc["_errors"]
	if ok {
		jc["_errors"] = append(ei.([]error), err)
	} else {
		jc["_errors"] = []error{err}
	}
}

// UnknownKeys returns the keys from the config that have not yet been discovered by one of the RequiredT or OptionalT calls.
func (jc Obj) UnknownKeys() []string {
	ei, ok := jc["_knownkeys"]
	var known map[string]bool
	if ok {
		known = ei.(map[string]bool)
	}
	var unknown []string
	for k, _ := range jc {
		if ok && known[k] {
			continue
		}
		if strings.HasPrefix(k, "_") {
			// Permit keys with a leading underscore as a
			// form of comments.
			continue
		}
		unknown = append(unknown, k)
	}
	sort.Strings(unknown)
	return unknown
}

func (jc Obj) Validate() error {
	unknown := jc.UnknownKeys()
	for _, k := range unknown {
		jc.appendError(fmt.Errorf("Unknown key %q", k))
	}

	ei, ok := jc["_errors"]
	if !ok {
		return nil
	}
	errList := ei.([]error)
	if len(errList) == 1 {
		return errList[0]
	}
	strs := make([]string, 0)
	for _, v := range errList {
		strs = append(strs, v.Error())
	}
	return fmt.Errorf("Multiple errors: " + strings.Join(strs, ", "))
}
<commit_msg>jsonconfig: remove useless type conversion<commit_after>/*
Copyright 2011 Google Inc.

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

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

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

// Package jsonconfig defines a helper type for JSON objects to be
// used for configuration.
package jsonconfig

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

// Obj is a JSON configuration map.
type Obj map[string]interface{}

// Reads json config data from the specified open file, expanding
// all expressions
func ReadFile(configPath string) (Obj, error) {
	var c ConfigParser
	return c.ReadFile(configPath)
}

func (jc Obj) RequiredObject(key string) Obj {
	return jc.obj(key, false)
}

func (jc Obj) OptionalObject(key string) Obj {
	return jc.obj(key, true)
}

func (jc Obj) obj(key string, optional bool) Obj {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if optional {
			return make(Obj)
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (object)", key))
		return make(Obj)
	}
	m, ok := ei.(map[string]interface{})
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be an object, not %T", key, ei))
		return make(Obj)
	}
	return m
}

func (jc Obj) RequiredString(key string) string {
	return jc.string(key, nil)
}

func (jc Obj) OptionalString(key, def string) string {
	return jc.string(key, &def)
}

func (jc Obj) string(key string, def *string) string {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if def != nil {
			return *def
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (string)", key))
		return ""
	}
	s, ok := ei.(string)
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be a string", key))
		return ""
	}
	return s
}

func (jc Obj) RequiredStringOrObject(key string) interface{} {
	return jc.stringOrObject(key, true)
}

func (jc Obj) OptionalStringOrObject(key string) interface{} {
	return jc.stringOrObject(key, false)
}

func (jc Obj) stringOrObject(key string, required bool) interface{} {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if !required {
			return nil
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (string or object)", key))
		return ""
	}
	if _, ok := ei.(map[string]interface{}); ok {
		return ei
	}
	if _, ok := ei.(string); ok {
		return ei
	}
	jc.appendError(fmt.Errorf("Expected config key %q to be a string or object", key))
	return ""
}

func (jc Obj) RequiredBool(key string) bool {
	return jc.bool(key, nil)
}

func (jc Obj) OptionalBool(key string, def bool) bool {
	return jc.bool(key, &def)
}

func (jc Obj) bool(key string, def *bool) bool {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if def != nil {
			return *def
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (boolean)", key))
		return false
	}
	switch v := ei.(type) {
	case bool:
		return v
	case string:
		b, err := strconv.ParseBool(v)
		if err != nil {
			jc.appendError(fmt.Errorf("Config key %q has bad boolean format %q", key, v))
		}
		return b
	default:
		jc.appendError(fmt.Errorf("Expected config key %q to be a boolean", key))
		return false
	}
}

func (jc Obj) RequiredInt(key string) int {
	return jc.int(key, nil)
}

func (jc Obj) OptionalInt(key string, def int) int {
	return jc.int(key, &def)
}

func (jc Obj) int(key string, def *int) int {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if def != nil {
			return *def
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key))
		return 0
	}
	b, ok := ei.(float64)
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be a number", key))
		return 0
	}
	return int(b)
}

func (jc Obj) RequiredInt64(key string) int64 {
	return jc.int64(key, nil)
}

func (jc Obj) OptionalInt64(key string, def int64) int64 {
	return jc.int64(key, &def)
}

func (jc Obj) int64(key string, def *int64) int64 {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if def != nil {
			return *def
		}
		jc.appendError(fmt.Errorf("Missing required config key %q (integer)", key))
		return 0
	}
	b, ok := ei.(float64)
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be a number", key))
		return 0
	}
	return int64(b)
}

func (jc Obj) RequiredList(key string) []string {
	return jc.requiredList(key, true)
}

func (jc Obj) OptionalList(key string) []string {
	return jc.requiredList(key, false)
}

func (jc Obj) requiredList(key string, required bool) []string {
	jc.noteKnownKey(key)
	ei, ok := jc[key]
	if !ok {
		if required {
			jc.appendError(fmt.Errorf("Missing required config key %q (list of strings)", key))
		}
		return nil
	}
	eil, ok := ei.([]interface{})
	if !ok {
		jc.appendError(fmt.Errorf("Expected config key %q to be a list, not %T", key, ei))
		return nil
	}
	sl := make([]string, len(eil))
	for i, ei := range eil {
		s, ok := ei.(string)
		if !ok {
			jc.appendError(fmt.Errorf("Expected config key %q index %d to be a string, not %T", key, i, ei))
			return nil
		}
		sl[i] = s
	}
	return sl
}

func (jc Obj) noteKnownKey(key string) {
	_, ok := jc["_knownkeys"]
	if !ok {
		jc["_knownkeys"] = make(map[string]bool)
	}
	jc["_knownkeys"].(map[string]bool)[key] = true
}

func (jc Obj) appendError(err error) {
	ei, ok := jc["_errors"]
	if ok {
		jc["_errors"] = append(ei.([]error), err)
	} else {
		jc["_errors"] = []error{err}
	}
}

// UnknownKeys returns the keys from the config that have not yet been discovered by one of the RequiredT or OptionalT calls.
func (jc Obj) UnknownKeys() []string {
	ei, ok := jc["_knownkeys"]
	var known map[string]bool
	if ok {
		known = ei.(map[string]bool)
	}
	var unknown []string
	for k, _ := range jc {
		if ok && known[k] {
			continue
		}
		if strings.HasPrefix(k, "_") {
			// Permit keys with a leading underscore as a
			// form of comments.
			continue
		}
		unknown = append(unknown, k)
	}
	sort.Strings(unknown)
	return unknown
}

func (jc Obj) Validate() error {
	unknown := jc.UnknownKeys()
	for _, k := range unknown {
		jc.appendError(fmt.Errorf("Unknown key %q", k))
	}

	ei, ok := jc["_errors"]
	if !ok {
		return nil
	}
	errList := ei.([]error)
	if len(errList) == 1 {
		return errList[0]
	}
	strs := make([]string, 0)
	for _, v := range errList {
		strs = append(strs, v.Error())
	}
	return fmt.Errorf("Multiple errors: " + strings.Join(strs, ", "))
}
<|endoftext|>


<commit_before>package config

import (
	"testing"

	"github.com/flimzy/diff"
)

func TestNewFromJSON(t *testing.T) {
	c, err := NewFromJSON([]byte(`{"foo":"bar"}`))
	if err != nil {
		t.Fatal(err)
	}
	expected := &Conf{
		c: map[string]string{"foo": "bar"},
	}
	if d := diff.Interface(expected, c); d != "" {
		t.Error(d)
	}
}

func TestNewFromInvalidJSON(t *testing.T) {
	_, err := NewFromJSON([]byte(`invalid json`))
	if err == nil {
		t.Fatal("Expected an error")
	}
}

func TestNew(t *testing.T) {
	c := New(map[string]string{"foo": "bar"})
	expected := &Conf{
		c: map[string]string{"foo": "bar"},
	}
	if d := diff.Interface(expected, c); d != "" {
		t.Error(d)
	}
}

func TestIsSet(t *testing.T) {
	c := New(map[string]string{"foo": "bar"})
	if !c.IsSet("foo") {
		t.Error("Expected 'foo' to be set")
	}
	if c.IsSet("bar") {
		t.Error("Expected 'bar' not to be set")
	}
}

func TestGetString(t *testing.T) {
	c := New(map[string]string{"foo": "bar"})
	if c.GetString("foo") != "bar" {
		t.Error("Expected 'bar' to be returned")
	}
	if c.GetString("bar") != "" {
		t.Error("Expected '' to be returned")
	}
}
<commit_msg>Update more tests for latest diff lib<commit_after>package config

import (
	"testing"

	"github.com/flimzy/diff"
)

func TestNewFromJSON(t *testing.T) {
	c, err := NewFromJSON([]byte(`{"foo":"bar"}`))
	if err != nil {
		t.Fatal(err)
	}
	expected := &Conf{
		c: map[string]string{"foo": "bar"},
	}
	if d := diff.Interface(expected, c); d != nil {
		t.Error(d)
	}
}

func TestNewFromInvalidJSON(t *testing.T) {
	_, err := NewFromJSON([]byte(`invalid json`))
	if err == nil {
		t.Fatal("Expected an error")
	}
}

func TestNew(t *testing.T) {
	c := New(map[string]string{"foo": "bar"})
	expected := &Conf{
		c: map[string]string{"foo": "bar"},
	}
	if d := diff.Interface(expected, c); d != nil {
		t.Error(d)
	}
}

func TestIsSet(t *testing.T) {
	c := New(map[string]string{"foo": "bar"})
	if !c.IsSet("foo") {
		t.Error("Expected 'foo' to be set")
	}
	if c.IsSet("bar") {
		t.Error("Expected 'bar' not to be set")
	}
}

func TestGetString(t *testing.T) {
	c := New(map[string]string{"foo": "bar"})
	if c.GetString("foo") != "bar" {
		t.Error("Expected 'bar' to be returned")
	}
	if c.GetString("bar") != "" {
		t.Error("Expected '' to be returned")
	}
}
<|endoftext|>


<commit_before>package common

import (
	"fmt"
	"net"
	"reflect"
	"strings"
	"time"

	"github.com/golang/glog"

	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	networkapi "github.com/openshift/origin/pkg/network/apis/network"
	networkclient "github.com/openshift/origin/pkg/network/generated/internalclientset"
	"github.com/openshift/origin/pkg/util/netutils"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	kerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/watch"
	kcache "k8s.io/client-go/tools/cache"
	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"
	kinternalinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
)

func HostSubnetToString(subnet *networkapi.HostSubnet) string {
	return fmt.Sprintf("%s (host: %q, ip: %q, subnet: %q)", subnet.Name, subnet.Host, subnet.HostIP, subnet.Subnet)
}

func ClusterNetworkToString(n *networkapi.ClusterNetwork) string {
	return fmt.Sprintf("%s (network: %q, hostSubnetBits: %d, serviceNetwork: %q, pluginName: %q)", n.Name, n.Network, n.HostSubnetLength, n.ServiceNetwork, n.PluginName)
}

func ClusterNetworkListContains(clusterNetworks []ClusterNetwork, ipaddr net.IP) (*net.IPNet, bool) {
	for _, cn := range clusterNetworks {
		if cn.ClusterCIDR.Contains(ipaddr) {
			return cn.ClusterCIDR, true
		}
	}
	return nil, false
}

type NetworkInfo struct {
	ClusterNetworks []ClusterNetwork
	ServiceNetwork  *net.IPNet
}

type ClusterNetwork struct {
	ClusterCIDR      *net.IPNet
	HostSubnetLength uint32
}

func ParseNetworkInfo(clusterNetwork []networkapi.ClusterNetworkEntry, serviceNetwork string) (*NetworkInfo, error) {
	var cns []ClusterNetwork

	for _, entry := range clusterNetwork {
		cidr, err := netutils.ParseCIDRMask(entry.CIDR)
		if err != nil {
			_, cidr, err := net.ParseCIDR(entry.CIDR)
			if err != nil {
				return nil, fmt.Errorf("failed to parse ClusterNetwork CIDR %s: %v", entry.CIDR, err)
			}
			glog.Errorf("Configured clusterNetworks value %q is invalid; treating it as %q", entry.CIDR, cidr.String())
			cns = append(cns, ClusterNetwork{ClusterCIDR: cidr, HostSubnetLength: entry.HostSubnetLength})
		} else {
			cns = append(cns, ClusterNetwork{ClusterCIDR: cidr, HostSubnetLength: entry.HostSubnetLength})
		}
	}

	sn, err := netutils.ParseCIDRMask(serviceNetwork)
	if err != nil {
		_, sn, err := net.ParseCIDR(serviceNetwork)
		if err != nil {
			return nil, fmt.Errorf("failed to parse ServiceNetwork CIDR %s: %v", serviceNetwork, err)
		}
		glog.Errorf("Configured serviceNetworkCIDR value %q is invalid; treating it as %q", serviceNetwork, sn.String())
	}

	return &NetworkInfo{
		ClusterNetworks: cns,
		ServiceNetwork:  sn,
	}, nil
}

func (ni *NetworkInfo) ValidateNodeIP(nodeIP string) error {
	if nodeIP == "" || nodeIP == "127.0.0.1" {
		return fmt.Errorf("invalid node IP %q", nodeIP)
	}

	// Ensure each node's NodeIP is not contained by the cluster network,
	// which could cause a routing loop. (rhbz#1295486)
	ipaddr := net.ParseIP(nodeIP)
	if ipaddr == nil {
		return fmt.Errorf("failed to parse node IP %s", nodeIP)
	}

	if conflictingCIDR, found := ClusterNetworkListContains(ni.ClusterNetworks, ipaddr); found {
		return fmt.Errorf("node IP %s conflicts with cluster network %s", nodeIP, conflictingCIDR.String())
	}
	if ni.ServiceNetwork.Contains(ipaddr) {
		return fmt.Errorf("node IP %s conflicts with service network %s", nodeIP, ni.ServiceNetwork.String())
	}

	return nil
}

func (ni *NetworkInfo) CheckHostNetworks(hostIPNets []*net.IPNet) error {
	errList := []error{}
	for _, ipNet := range hostIPNets {
		for _, clusterNetwork := range ni.ClusterNetworks {
			if configapi.CIDRsOverlap(ipNet.String(), clusterNetwork.ClusterCIDR.String()) {
				errList = append(errList, fmt.Errorf("cluster IP: %s conflicts with host network: %s", clusterNetwork.ClusterCIDR.IP.String(), ipNet.String()))
			}
		}
		if configapi.CIDRsOverlap(ipNet.String(), ni.ServiceNetwork.String()) {
			errList = append(errList, fmt.Errorf("service IP: %s conflicts with host network: %s", ni.ServiceNetwork.String(), ipNet.String()))
		}
	}
	return kerrors.NewAggregate(errList)
}

func (ni *NetworkInfo) CheckClusterObjects(subnets []networkapi.HostSubnet, pods []kapi.Pod, services []kapi.Service) error {
	var errList []error

	for _, subnet := range subnets {
		subnetIP, _, _ := net.ParseCIDR(subnet.Subnet)
		if subnetIP == nil {
			errList = append(errList, fmt.Errorf("failed to parse network address: %s", subnet.Subnet))
		} else if _, contains := ClusterNetworkListContains(ni.ClusterNetworks, subnetIP); !contains {
			errList = append(errList, fmt.Errorf("existing node subnet: %s is not part of any cluster network CIDR", subnet.Subnet))
		}
		if len(errList) >= 10 {
			break
		}
	}
	for _, pod := range pods {
		if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork {
			continue
		}
		if _, contains := ClusterNetworkListContains(ni.ClusterNetworks, net.ParseIP(pod.Status.PodIP)); !contains && pod.Status.PodIP != "" {
			errList = append(errList, fmt.Errorf("existing pod %s:%s with IP %s is not part of cluster network", pod.Namespace, pod.Name, pod.Status.PodIP))
			if len(errList) >= 10 {
				break
			}
		}
	}
	for _, svc := range services {
		svcIP := net.ParseIP(svc.Spec.ClusterIP)
		if svcIP != nil && !ni.ServiceNetwork.Contains(svcIP) {
			errList = append(errList, fmt.Errorf("existing service %s:%s with IP %s is not part of service network %s", svc.Namespace, svc.Name, svc.Spec.ClusterIP, ni.ServiceNetwork.String()))
			if len(errList) >= 10 {
				break
			}
		}
	}

	if len(errList) >= 10 {
		errList = append(errList, fmt.Errorf("too many errors... truncating"))
	}
	return kerrors.NewAggregate(errList)
}

func GetNetworkInfo(networkClient networkclient.Interface) (*NetworkInfo, error) {
	cn, err := networkClient.Network().ClusterNetworks().Get(networkapi.ClusterNetworkDefault, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}

	return ParseNetworkInfo(cn.ClusterNetworks, cn.ServiceNetwork)
}

type ResourceName string

const (
	Nodes                 ResourceName = "Nodes"
	Namespaces            ResourceName = "Namespaces"
	NetNamespaces         ResourceName = "NetNamespaces"
	Services              ResourceName = "Services"
	HostSubnets           ResourceName = "HostSubnets"
	Pods                  ResourceName = "Pods"
	EgressNetworkPolicies ResourceName = "EgressNetworkPolicies"
	NetworkPolicies       ResourceName = "NetworkPolicies"
)

func newEventQueue(client kcache.Getter, resourceName ResourceName, expectedType interface{}, namespace string) *EventQueue {
	rn := strings.ToLower(string(resourceName))
	lw := kcache.NewListWatchFromClient(client, rn, namespace, fields.Everything())
	eventQueue := NewEventQueue(DeletionHandlingMetaNamespaceKeyFunc)
	// Repopulate event queue every 30 mins
	// Existing items in the event queue will have watch.Modified event type
	kcache.NewReflector(lw, expectedType, eventQueue, 30*time.Minute).Run()
	return eventQueue
}

// Run event queue for the given resource. The 'process' function is called
// repeatedly with each available cache.Delta that describes state changes
// to an object. If the process function returns an error queued changes
// for that object are dropped but processing continues with the next available
// object's cache.Deltas.  The error is logged with call stack information.
//
// NOTE: this function will handle DeletedFinalStateUnknown delta objects
// automatically, which may not always be what you want since the now-deleted
// object may be stale.
func RunEventQueue(client kcache.Getter, resourceName ResourceName, process ProcessEventFunc) {
	var expectedType interface{}

	switch resourceName {
	case HostSubnets:
		expectedType = &networkapi.HostSubnet{}
	case NetNamespaces:
		expectedType = &networkapi.NetNamespace{}
	case EgressNetworkPolicies:
		expectedType = &networkapi.EgressNetworkPolicy{}
	case NetworkPolicies:
		expectedType = &extensions.NetworkPolicy{}
	default:
		glog.Fatalf("Unknown resource %s during initialization of event queue", resourceName)
	}

	eventQueue := newEventQueue(client, resourceName, expectedType, metav1.NamespaceAll)
	for {
		eventQueue.Pop(process, expectedType)
	}
}

// RegisterSharedInformerEventHandlers registers addOrUpdateFunc and delFunc event handlers with
// kubernetes shared informers for the given resource name.
func RegisterSharedInformerEventHandlers(kubeInformers kinternalinformers.SharedInformerFactory,
	addOrUpdateFunc func(interface{}, interface{}, watch.EventType),
	delFunc func(interface{}), resourceName ResourceName) {

	var expectedObjType interface{}
	var informer kcache.SharedIndexInformer

	internalVersion := kubeInformers.Core().InternalVersion()

	switch resourceName {
	case Nodes:
		informer = internalVersion.Nodes().Informer()
		expectedObjType = &kapi.Node{}
	case Namespaces:
		informer = internalVersion.Namespaces().Informer()
		expectedObjType = &kapi.Namespace{}
	case Services:
		informer = internalVersion.Services().Informer()
		expectedObjType = &kapi.Service{}
	case Pods:
		informer = internalVersion.Pods().Informer()
		expectedObjType = &kapi.Pod{}
	default:
		glog.Errorf("Unknown resource name: %s", resourceName)
		return
	}

	informer.AddEventHandler(kcache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			addOrUpdateFunc(obj, nil, watch.Added)
		},
		UpdateFunc: func(old, cur interface{}) {
			addOrUpdateFunc(cur, old, watch.Modified)
		},
		DeleteFunc: func(obj interface{}) {
			if reflect.TypeOf(expectedObjType) != reflect.TypeOf(obj) {
				tombstone, ok := obj.(kcache.DeletedFinalStateUnknown)
				if !ok {
					glog.Errorf("Couldn't get object from tombstone: %+v", obj)
					return
				}

				obj = tombstone.Obj
				if reflect.TypeOf(expectedObjType) != reflect.TypeOf(obj) {
					glog.Errorf("Tombstone contained object that is not a %s: %+v", resourceName, obj)
					return
				}
			}
			delFunc(obj)
		},
	})
}
<commit_msg>Fix crash with invalid serviceNetworkCIDR<commit_after>package common

import (
	"fmt"
	"net"
	"reflect"
	"strings"
	"time"

	"github.com/golang/glog"

	configapi "github.com/openshift/origin/pkg/cmd/server/api"
	networkapi "github.com/openshift/origin/pkg/network/apis/network"
	networkclient "github.com/openshift/origin/pkg/network/generated/internalclientset"
	"github.com/openshift/origin/pkg/util/netutils"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	kerrors "k8s.io/apimachinery/pkg/util/errors"
	"k8s.io/apimachinery/pkg/watch"
	kcache "k8s.io/client-go/tools/cache"
	kapi "k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/apis/extensions"
	kinternalinformers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
)

func HostSubnetToString(subnet *networkapi.HostSubnet) string {
	return fmt.Sprintf("%s (host: %q, ip: %q, subnet: %q)", subnet.Name, subnet.Host, subnet.HostIP, subnet.Subnet)
}

func ClusterNetworkToString(n *networkapi.ClusterNetwork) string {
	return fmt.Sprintf("%s (network: %q, hostSubnetBits: %d, serviceNetwork: %q, pluginName: %q)", n.Name, n.Network, n.HostSubnetLength, n.ServiceNetwork, n.PluginName)
}

func ClusterNetworkListContains(clusterNetworks []ClusterNetwork, ipaddr net.IP) (*net.IPNet, bool) {
	for _, cn := range clusterNetworks {
		if cn.ClusterCIDR.Contains(ipaddr) {
			return cn.ClusterCIDR, true
		}
	}
	return nil, false
}

type NetworkInfo struct {
	ClusterNetworks []ClusterNetwork
	ServiceNetwork  *net.IPNet
}

type ClusterNetwork struct {
	ClusterCIDR      *net.IPNet
	HostSubnetLength uint32
}

func ParseNetworkInfo(clusterNetwork []networkapi.ClusterNetworkEntry, serviceNetwork string) (*NetworkInfo, error) {
	var cns []ClusterNetwork

	for _, entry := range clusterNetwork {
		cidr, err := netutils.ParseCIDRMask(entry.CIDR)
		if err != nil {
			_, cidr, err = net.ParseCIDR(entry.CIDR)
			if err != nil {
				return nil, fmt.Errorf("failed to parse ClusterNetwork CIDR %s: %v", entry.CIDR, err)
			}
			glog.Errorf("Configured clusterNetworks value %q is invalid; treating it as %q", entry.CIDR, cidr.String())
		}
		cns = append(cns, ClusterNetwork{ClusterCIDR: cidr, HostSubnetLength: entry.HostSubnetLength})
	}

	sn, err := netutils.ParseCIDRMask(serviceNetwork)
	if err != nil {
		_, sn, err = net.ParseCIDR(serviceNetwork)
		if err != nil {
			return nil, fmt.Errorf("failed to parse ServiceNetwork CIDR %s: %v", serviceNetwork, err)
		}
		glog.Errorf("Configured serviceNetworkCIDR value %q is invalid; treating it as %q", serviceNetwork, sn.String())
	}

	return &NetworkInfo{
		ClusterNetworks: cns,
		ServiceNetwork:  sn,
	}, nil
}

func (ni *NetworkInfo) ValidateNodeIP(nodeIP string) error {
	if nodeIP == "" || nodeIP == "127.0.0.1" {
		return fmt.Errorf("invalid node IP %q", nodeIP)
	}

	// Ensure each node's NodeIP is not contained by the cluster network,
	// which could cause a routing loop. (rhbz#1295486)
	ipaddr := net.ParseIP(nodeIP)
	if ipaddr == nil {
		return fmt.Errorf("failed to parse node IP %s", nodeIP)
	}

	if conflictingCIDR, found := ClusterNetworkListContains(ni.ClusterNetworks, ipaddr); found {
		return fmt.Errorf("node IP %s conflicts with cluster network %s", nodeIP, conflictingCIDR.String())
	}
	if ni.ServiceNetwork.Contains(ipaddr) {
		return fmt.Errorf("node IP %s conflicts with service network %s", nodeIP, ni.ServiceNetwork.String())
	}

	return nil
}

func (ni *NetworkInfo) CheckHostNetworks(hostIPNets []*net.IPNet) error {
	errList := []error{}
	for _, ipNet := range hostIPNets {
		for _, clusterNetwork := range ni.ClusterNetworks {
			if configapi.CIDRsOverlap(ipNet.String(), clusterNetwork.ClusterCIDR.String()) {
				errList = append(errList, fmt.Errorf("cluster IP: %s conflicts with host network: %s", clusterNetwork.ClusterCIDR.IP.String(), ipNet.String()))
			}
		}
		if configapi.CIDRsOverlap(ipNet.String(), ni.ServiceNetwork.String()) {
			errList = append(errList, fmt.Errorf("service IP: %s conflicts with host network: %s", ni.ServiceNetwork.String(), ipNet.String()))
		}
	}
	return kerrors.NewAggregate(errList)
}

func (ni *NetworkInfo) CheckClusterObjects(subnets []networkapi.HostSubnet, pods []kapi.Pod, services []kapi.Service) error {
	var errList []error

	for _, subnet := range subnets {
		subnetIP, _, _ := net.ParseCIDR(subnet.Subnet)
		if subnetIP == nil {
			errList = append(errList, fmt.Errorf("failed to parse network address: %s", subnet.Subnet))
		} else if _, contains := ClusterNetworkListContains(ni.ClusterNetworks, subnetIP); !contains {
			errList = append(errList, fmt.Errorf("existing node subnet: %s is not part of any cluster network CIDR", subnet.Subnet))
		}
		if len(errList) >= 10 {
			break
		}
	}
	for _, pod := range pods {
		if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostNetwork {
			continue
		}
		if _, contains := ClusterNetworkListContains(ni.ClusterNetworks, net.ParseIP(pod.Status.PodIP)); !contains && pod.Status.PodIP != "" {
			errList = append(errList, fmt.Errorf("existing pod %s:%s with IP %s is not part of cluster network", pod.Namespace, pod.Name, pod.Status.PodIP))
			if len(errList) >= 10 {
				break
			}
		}
	}
	for _, svc := range services {
		svcIP := net.ParseIP(svc.Spec.ClusterIP)
		if svcIP != nil && !ni.ServiceNetwork.Contains(svcIP) {
			errList = append(errList, fmt.Errorf("existing service %s:%s with IP %s is not part of service network %s", svc.Namespace, svc.Name, svc.Spec.ClusterIP, ni.ServiceNetwork.String()))
			if len(errList) >= 10 {
				break
			}
		}
	}

	if len(errList) >= 10 {
		errList = append(errList, fmt.Errorf("too many errors... truncating"))
	}
	return kerrors.NewAggregate(errList)
}

func GetNetworkInfo(networkClient networkclient.Interface) (*NetworkInfo, error) {
	cn, err := networkClient.Network().ClusterNetworks().Get(networkapi.ClusterNetworkDefault, metav1.GetOptions{})
	if err != nil {
		return nil, err
	}

	return ParseNetworkInfo(cn.ClusterNetworks, cn.ServiceNetwork)
}

type ResourceName string

const (
	Nodes                 ResourceName = "Nodes"
	Namespaces            ResourceName = "Namespaces"
	NetNamespaces         ResourceName = "NetNamespaces"
	Services              ResourceName = "Services"
	HostSubnets           ResourceName = "HostSubnets"
	Pods                  ResourceName = "Pods"
	EgressNetworkPolicies ResourceName = "EgressNetworkPolicies"
	NetworkPolicies       ResourceName = "NetworkPolicies"
)

func newEventQueue(client kcache.Getter, resourceName ResourceName, expectedType interface{}, namespace string) *EventQueue {
	rn := strings.ToLower(string(resourceName))
	lw := kcache.NewListWatchFromClient(client, rn, namespace, fields.Everything())
	eventQueue := NewEventQueue(DeletionHandlingMetaNamespaceKeyFunc)
	// Repopulate event queue every 30 mins
	// Existing items in the event queue will have watch.Modified event type
	kcache.NewReflector(lw, expectedType, eventQueue, 30*time.Minute).Run()
	return eventQueue
}

// Run event queue for the given resource. The 'process' function is called
// repeatedly with each available cache.Delta that describes state changes
// to an object. If the process function returns an error queued changes
// for that object are dropped but processing continues with the next available
// object's cache.Deltas.  The error is logged with call stack information.
//
// NOTE: this function will handle DeletedFinalStateUnknown delta objects
// automatically, which may not always be what you want since the now-deleted
// object may be stale.
func RunEventQueue(client kcache.Getter, resourceName ResourceName, process ProcessEventFunc) {
	var expectedType interface{}

	switch resourceName {
	case HostSubnets:
		expectedType = &networkapi.HostSubnet{}
	case NetNamespaces:
		expectedType = &networkapi.NetNamespace{}
	case EgressNetworkPolicies:
		expectedType = &networkapi.EgressNetworkPolicy{}
	case NetworkPolicies:
		expectedType = &extensions.NetworkPolicy{}
	default:
		glog.Fatalf("Unknown resource %s during initialization of event queue", resourceName)
	}

	eventQueue := newEventQueue(client, resourceName, expectedType, metav1.NamespaceAll)
	for {
		eventQueue.Pop(process, expectedType)
	}
}

// RegisterSharedInformerEventHandlers registers addOrUpdateFunc and delFunc event handlers with
// kubernetes shared informers for the given resource name.
func RegisterSharedInformerEventHandlers(kubeInformers kinternalinformers.SharedInformerFactory,
	addOrUpdateFunc func(interface{}, interface{}, watch.EventType),
	delFunc func(interface{}), resourceName ResourceName) {

	var expectedObjType interface{}
	var informer kcache.SharedIndexInformer

	internalVersion := kubeInformers.Core().InternalVersion()

	switch resourceName {
	case Nodes:
		informer = internalVersion.Nodes().Informer()
		expectedObjType = &kapi.Node{}
	case Namespaces:
		informer = internalVersion.Namespaces().Informer()
		expectedObjType = &kapi.Namespace{}
	case Services:
		informer = internalVersion.Services().Informer()
		expectedObjType = &kapi.Service{}
	case Pods:
		informer = internalVersion.Pods().Informer()
		expectedObjType = &kapi.Pod{}
	default:
		glog.Errorf("Unknown resource name: %s", resourceName)
		return
	}

	informer.AddEventHandler(kcache.ResourceEventHandlerFuncs{
		AddFunc: func(obj interface{}) {
			addOrUpdateFunc(obj, nil, watch.Added)
		},
		UpdateFunc: func(old, cur interface{}) {
			addOrUpdateFunc(cur, old, watch.Modified)
		},
		DeleteFunc: func(obj interface{}) {
			if reflect.TypeOf(expectedObjType) != reflect.TypeOf(obj) {
				tombstone, ok := obj.(kcache.DeletedFinalStateUnknown)
				if !ok {
					glog.Errorf("Couldn't get object from tombstone: %+v", obj)
					return
				}

				obj = tombstone.Obj
				if reflect.TypeOf(expectedObjType) != reflect.TypeOf(obj) {
					glog.Errorf("Tombstone contained object that is not a %s: %+v", resourceName, obj)
					return
				}
			}
			delFunc(obj)
		},
	})
}
<|endoftext|>


<commit_before>// This file contains struct mappings to unmarshal three different PRONOM XML formats: the signature file format, the report format, and the container format
package mappings

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"strings"
)

// Droid Signature File

type Droid struct {
	XMLName     xml.Name     `xml:"FFSignatureFile"`
	Version     int          `xml:",attr"`
	FileFormats []FileFormat `xml:"FileFormatCollection>FileFormat"`
}

func (d Droid) String() string {
	buf := new(bytes.Buffer)
	for _, v := range d.FileFormats {
		fmt.Fprintln(buf, v)
	}
	return buf.String()
}

type FileFormat struct {
	ID         int      `xml:",attr"`
	Puid       string   `xml:"PUID,attr"`
	Name       string   `xml:",attr"`
	Version    string   `xml:",attr"`
	MIMEType   string   `xml:",attr"`
	Extensions []string `xml:"Extension"`
	Priorities []int    `xml:"HasPriorityOverFileFormatID"`
	*Report
}

func (f FileFormat) String() string {
	null := func(s string) string {
		if strings.TrimSpace(s) == "" {
			return "NULL"
		}
		return s
	}
	buf := new(bytes.Buffer)
	fmt.Fprintf(buf, "Puid: %s; Name: %s; Version: %s; Ext(s): %s\n", f.Puid, f.Name, f.Version, strings.Join(f.Extensions, ", "))
	fmt.Fprintln(buf, f.Description)
	for _, v := range f.Signatures {
		fmt.Fprint(buf, "Signature\n")
		for _, v1 := range v.ByteSequences {
			fmt.Fprintf(buf, "Position: %s, Offset: %s, MaxOffset: %s, IndirectLoc: %s, IndirectLen: %s, Endianness: %s\n",
				null(v1.Position), null(v1.Offset), null(v1.MaxOffset), null(v1.IndirectLoc), null(v1.IndirectLen), null(v1.Endianness))
			fmt.Fprintln(buf, v1.Hex)
		}
	}
	return buf.String()
}
<commit_msg>give explicit name to FileFormat to fix for gotip<commit_after>// This file contains struct mappings to unmarshal three different PRONOM XML formats: the signature file format, the report format, and the container format
package mappings

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"strings"
)

// Droid Signature File

type Droid struct {
	XMLName     xml.Name     `xml:"FFSignatureFile"`
	Version     int          `xml:",attr"`
	FileFormats []FileFormat `xml:"FileFormatCollection>FileFormat"`
}

func (d Droid) String() string {
	buf := new(bytes.Buffer)
	for _, v := range d.FileFormats {
		fmt.Fprintln(buf, v)
	}
	return buf.String()
}

type FileFormat struct {
	XMLName    xml.Name `xml:"FileFormat"`
	ID         int      `xml:",attr"`
	Puid       string   `xml:"PUID,attr"`
	Name       string   `xml:",attr"`
	Version    string   `xml:",attr"`
	MIMEType   string   `xml:",attr"`
	Extensions []string `xml:"Extension"`
	Priorities []int    `xml:"HasPriorityOverFileFormatID"`
	*Report
}

func (f FileFormat) String() string {
	null := func(s string) string {
		if strings.TrimSpace(s) == "" {
			return "NULL"
		}
		return s
	}
	buf := new(bytes.Buffer)
	fmt.Fprintf(buf, "Puid: %s; Name: %s; Version: %s; Ext(s): %s\n", f.Puid, f.Name, f.Version, strings.Join(f.Extensions, ", "))
	fmt.Fprintln(buf, f.Description)
	for _, v := range f.Signatures {
		fmt.Fprint(buf, "Signature\n")
		for _, v1 := range v.ByteSequences {
			fmt.Fprintf(buf, "Position: %s, Offset: %s, MaxOffset: %s, IndirectLoc: %s, IndirectLen: %s, Endianness: %s\n",
				null(v1.Position), null(v1.Offset), null(v1.MaxOffset), null(v1.IndirectLoc), null(v1.IndirectLen), null(v1.Endianness))
			fmt.Fprintln(buf, v1.Hex)
		}
	}
	return buf.String()
}
<|endoftext|>


<commit_before>/*
Copyright 2018 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 docker

import (
	"context"
	"io"
	"net/http"

	"github.com/google/go-containerregistry/authn"
	"github.com/google/go-containerregistry/name"
	"github.com/google/go-containerregistry/v1"
	"github.com/google/go-containerregistry/v1/remote"
	"github.com/google/go-containerregistry/v1/remote/transport"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/pkg/progress"
	"github.com/moby/moby/pkg/jsonmessage"
	"github.com/moby/moby/pkg/streamformatter"
	"github.com/moby/moby/pkg/term"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type BuildOptions struct {
	ImageName   string
	Dockerfile  string
	ContextDir  string
	ProgressBuf io.Writer
	BuildBuf    io.Writer
	BuildArgs   map[string]*string
}

// RunBuild performs a docker build and returns nothing
func RunBuild(ctx context.Context, cli DockerAPIClient, opts *BuildOptions) error {
	logrus.Debugf("Running docker build: context: %s, dockerfile: %s", opts.ContextDir, opts.Dockerfile)
	authConfigs, err := DefaultAuthHelper.GetAllAuthConfigs()
	if err != nil {
		return errors.Wrap(err, "read auth configs")
	}

	imageBuildOpts := types.ImageBuildOptions{
		Tags:        []string{opts.ImageName},
		Dockerfile:  opts.Dockerfile,
		BuildArgs:   opts.BuildArgs,
		AuthConfigs: authConfigs,
	}

	buildCtx, buildCtxWriter := io.Pipe()
	go func() {
		err := CreateDockerTarContext(buildCtxWriter, opts.Dockerfile, opts.ContextDir)
		if err != nil {
			buildCtxWriter.CloseWithError(errors.Wrap(err, "creating docker context"))
			return
		}
		buildCtxWriter.Close()
	}()

	progressOutput := streamformatter.NewProgressOutput(opts.ProgressBuf)
	body := progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")

	resp, err := cli.ImageBuild(ctx, body, imageBuildOpts)
	if err != nil {
		return errors.Wrap(err, "docker build")
	}
	defer resp.Body.Close()
	return StreamDockerMessages(opts.BuildBuf, resp.Body)
}

// TODO(@r2d4): Make this output much better, this is the bare minimum
func StreamDockerMessages(dst io.Writer, src io.Reader) error {
	fd, _ := term.GetFdInfo(dst)
	return jsonmessage.DisplayJSONMessagesStream(src, dst, fd, false, nil)
}

func RunPush(ctx context.Context, cli DockerAPIClient, ref string, out io.Writer) error {
	registryAuth, err := encodedRegistryAuth(ctx, cli, DefaultAuthHelper, ref)
	if err != nil {
		return errors.Wrapf(err, "getting auth config for %s", ref)
	}
	rc, err := cli.ImagePush(ctx, ref, types.ImagePushOptions{
		RegistryAuth: registryAuth,
	})
	if err != nil {
		return errors.Wrap(err, "pushing image to repository")
	}
	defer rc.Close()
	return StreamDockerMessages(out, rc)
}

func AddTag(src, target string) error {
	srcRef, err := name.ParseReference(src, name.WeakValidation)
	if err != nil {
		return errors.Wrap(err, "getting source reference")
	}

	auth, err := authn.DefaultKeychain.Resolve(srcRef.Context().Registry)
	if err != nil {
		return err
	}

	targetRef, err := name.ParseReference(target, name.WeakValidation)
	if err != nil {
		return errors.Wrap(err, "getting target reference")
	}

	return addTag(srcRef, targetRef, auth, http.DefaultTransport)
}

func addTag(ref name.Reference, targetRef name.Reference, auth authn.Authenticator, t http.RoundTripper) error {
	tr, err := transport.New(ref, auth, t, transport.PushScope)
	if err != nil {
		return err
	}

	img, err := remote.Image(ref, auth, tr)
	if err != nil {
		return err
	}

	wo := remote.WriteOptions{
		MountPaths: []name.Repository{ref.Context()},
	}
	return remote.Write(targetRef, img, auth, t, wo)
}

// Digest returns the image digest for a corresponding reference.
// The digest is of the form
// sha256:<image_id>
func Digest(ctx context.Context, cli DockerAPIClient, ref string) (string, error) {
	args := filters.KeyValuePair{Key: "reference", Value: ref}
	filters := filters.NewArgs(args)
	imageList, err := cli.ImageList(ctx, types.ImageListOptions{
		Filters: filters,
	})
	if err != nil {
		return "", errors.Wrap(err, "getting image id")
	}
	for _, image := range imageList {
		for _, tag := range image.RepoTags {
			if tag == ref {
				return image.ID, nil
			}
		}
	}
	return "", nil
}

func remoteImage(identifier string) (v1.Image, error) {
	ref, err := name.ParseReference(identifier, name.WeakValidation)
	if err != nil {
		return nil, errors.Wrap(err, "parsing initial ref")
	}

	auth, err := authn.DefaultKeychain.Resolve(ref.Context().Registry)
	if err != nil {
		return nil, errors.Wrap(err, "getting default keychain auth")
	}

	return remote.Image(ref, auth, http.DefaultTransport)
}

func RemoteDigest(identifier string) (string, error) {
	img, err := remoteImage(identifier)
	if err != nil {
		return "", errors.Wrap(err, "getting image")
	}

	h, err := img.Digest()
	if err != nil {
		return "", errors.Wrap(err, "getting digest")
	}

	return h.String(), nil
}
<commit_msg>Manual changes for update.<commit_after>/*
Copyright 2018 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 docker

import (
	"context"
	"io"
	"net/http"

	"github.com/google/go-containerregistry/authn"
	"github.com/google/go-containerregistry/name"
	"github.com/google/go-containerregistry/v1"
	"github.com/google/go-containerregistry/v1/remote"
	"github.com/google/go-containerregistry/v1/remote/transport"

	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/filters"
	"github.com/docker/docker/pkg/progress"
	"github.com/moby/moby/pkg/jsonmessage"
	"github.com/moby/moby/pkg/streamformatter"
	"github.com/moby/moby/pkg/term"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

type BuildOptions struct {
	ImageName   string
	Dockerfile  string
	ContextDir  string
	ProgressBuf io.Writer
	BuildBuf    io.Writer
	BuildArgs   map[string]*string
}

// RunBuild performs a docker build and returns nothing
func RunBuild(ctx context.Context, cli DockerAPIClient, opts *BuildOptions) error {
	logrus.Debugf("Running docker build: context: %s, dockerfile: %s", opts.ContextDir, opts.Dockerfile)
	authConfigs, err := DefaultAuthHelper.GetAllAuthConfigs()
	if err != nil {
		return errors.Wrap(err, "read auth configs")
	}

	imageBuildOpts := types.ImageBuildOptions{
		Tags:        []string{opts.ImageName},
		Dockerfile:  opts.Dockerfile,
		BuildArgs:   opts.BuildArgs,
		AuthConfigs: authConfigs,
	}

	buildCtx, buildCtxWriter := io.Pipe()
	go func() {
		err := CreateDockerTarContext(buildCtxWriter, opts.Dockerfile, opts.ContextDir)
		if err != nil {
			buildCtxWriter.CloseWithError(errors.Wrap(err, "creating docker context"))
			return
		}
		buildCtxWriter.Close()
	}()

	progressOutput := streamformatter.NewProgressOutput(opts.ProgressBuf)
	body := progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")

	resp, err := cli.ImageBuild(ctx, body, imageBuildOpts)
	if err != nil {
		return errors.Wrap(err, "docker build")
	}
	defer resp.Body.Close()
	return StreamDockerMessages(opts.BuildBuf, resp.Body)
}

// TODO(@r2d4): Make this output much better, this is the bare minimum
func StreamDockerMessages(dst io.Writer, src io.Reader) error {
	fd, _ := term.GetFdInfo(dst)
	return jsonmessage.DisplayJSONMessagesStream(src, dst, fd, false, nil)
}

func RunPush(ctx context.Context, cli DockerAPIClient, ref string, out io.Writer) error {
	registryAuth, err := encodedRegistryAuth(ctx, cli, DefaultAuthHelper, ref)
	if err != nil {
		return errors.Wrapf(err, "getting auth config for %s", ref)
	}
	rc, err := cli.ImagePush(ctx, ref, types.ImagePushOptions{
		RegistryAuth: registryAuth,
	})
	if err != nil {
		return errors.Wrap(err, "pushing image to repository")
	}
	defer rc.Close()
	return StreamDockerMessages(out, rc)
}

func AddTag(src, target string) error {
	srcRef, err := name.ParseReference(src, name.WeakValidation)
	if err != nil {
		return errors.Wrap(err, "getting source reference")
	}

	auth, err := authn.DefaultKeychain.Resolve(srcRef.Context().Registry)
	if err != nil {
		return err
	}

	targetRef, err := name.ParseReference(target, name.WeakValidation)
	if err != nil {
		return errors.Wrap(err, "getting target reference")
	}

	return addTag(srcRef, targetRef, auth, http.DefaultTransport)
}

func addTag(ref name.Reference, targetRef name.Reference, auth authn.Authenticator, t http.RoundTripper) error {
	tr, err := transport.New(ref.Context().Registry, auth, t, []string{transport.PushScope})
	if err != nil {
		return err
	}

	img, err := remote.Image(ref, auth, tr)
	if err != nil {
		return err
	}

	wo := remote.WriteOptions{
		MountPaths: []name.Repository{ref.Context()},
	}
	return remote.Write(targetRef, img, auth, t, wo)
}

// Digest returns the image digest for a corresponding reference.
// The digest is of the form
// sha256:<image_id>
func Digest(ctx context.Context, cli DockerAPIClient, ref string) (string, error) {
	args := filters.KeyValuePair{Key: "reference", Value: ref}
	filters := filters.NewArgs(args)
	imageList, err := cli.ImageList(ctx, types.ImageListOptions{
		Filters: filters,
	})
	if err != nil {
		return "", errors.Wrap(err, "getting image id")
	}
	for _, image := range imageList {
		for _, tag := range image.RepoTags {
			if tag == ref {
				return image.ID, nil
			}
		}
	}
	return "", nil
}

func remoteImage(identifier string) (v1.Image, error) {
	ref, err := name.ParseReference(identifier, name.WeakValidation)
	if err != nil {
		return nil, errors.Wrap(err, "parsing initial ref")
	}

	auth, err := authn.DefaultKeychain.Resolve(ref.Context().Registry)
	if err != nil {
		return nil, errors.Wrap(err, "getting default keychain auth")
	}

	return remote.Image(ref, auth, http.DefaultTransport)
}

func RemoteDigest(identifier string) (string, error) {
	img, err := remoteImage(identifier)
	if err != nil {
		return "", errors.Wrap(err, "getting image")
	}

	h, err := img.Digest()
	if err != nil {
		return "", errors.Wrap(err, "getting digest")
	}

	return h.String(), nil
}
<|endoftext|>


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

// Package uefi contents data types for the components found in UEFI and an
// Parse function for reading an image.
package uefi

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"reflect"
)

var (
	// ReadOnly breaks firmware modification operations, but optimizes
	// memory and CPU consumption for read-only operations.
	//
	// DO NOT USE THIS OPTION UNLESS YOU KNOW WHAT ARE YOU DOING.
	ReadOnly = false

	// DisableDecompression disables section decompression.
	//
	// DO NOT USE THIS OPTION UNLESS YOU KNOW WHAT ARE YOU DOING.
	DisableDecompression = false
)

// ROMAttributes is used to hold global variables that apply across the whole image.
// We have to do this to avoid passing too many things down each time.
type ROMAttributes struct {
	ErasePolarity byte // Either 0xFF or 0
}

const poisonedPolarity byte = 0xF0

// Attributes holds the global attributes
var Attributes = ROMAttributes{ErasePolarity: poisonedPolarity}

// SetErasePolarity sets the Erase Polarity for the flash image.
// It checks to see if there are conflicting Erase Polarities.
func SetErasePolarity(ep byte) error {
	if ep != 0xFF && ep != 0 {
		// Invalid erase polarity requested.
		return fmt.Errorf("invalid erase polarity requested, should only be 0x00 or 0xFF, got 0x%02X",
			ep)
	}
	// Set it only once.
	if Attributes.ErasePolarity != poisonedPolarity {
		// it's already been set. Check that they are the same.
		if Attributes.ErasePolarity != ep {
			return fmt.Errorf("conflicting erase polarities, was 0x%02X, requested 0x%02X",
				Attributes.ErasePolarity, ep)
		}
		return nil
	}
	Attributes.ErasePolarity = ep
	return nil
}

// Firmware is an interface to describe generic firmware types. When the
// firmware is parsed, all the Firmware objects are laid out in a tree (similar
// to an AST). This interface represents one node in said tree. The
// implementations (e.g. Flash image, or FirmwareVolume) must implement this
// interface.
type Firmware interface {
	Buf() []byte
	SetBuf(buf []byte)

	// Apply a visitor to the Firmware.
	Apply(v Visitor) error

	// Apply a visitor to all the direct children of the Firmware
	// (excluding the Firmware itself).
	ApplyChildren(v Visitor) error
}

// TypedFirmware includes the Firmware interface's type when exporting it to
// JSON. The type is required when unmarshalling.
type TypedFirmware struct {
	Type  string
	Value Firmware
}

// MakeTyped takes a Firmware interface and makes a (type, value) pair.
func MakeTyped(f Firmware) *TypedFirmware {
	return &TypedFirmware{
		Type:  reflect.TypeOf(f).String(),
		Value: f,
	}
}

// UnmarshalJSON unmarshals a TypedFirmware struct and correctly deduces the
// type of the interface.
func (f *TypedFirmware) UnmarshalJSON(b []byte) error {
	var getType struct {
		Type  string
		Value json.RawMessage
	}
	if err := json.Unmarshal(b, &getType); err != nil {
		return err
	}
	factory, ok := firmwareTypes[getType.Type]
	if !ok {
		return fmt.Errorf("unknown TypedFirmware type '%s', unable to unmarshal", getType.Type)
	}
	f.Type = getType.Type
	f.Value = factory()
	return json.Unmarshal(getType.Value, &f.Value)
}

// This should never be exposed, it is only used for marshalling different types to json.
type marshalFirmware struct {
	FType           string
	FirmwareElement json.RawMessage
}

var firmwareTypes = map[string]func() Firmware{
	"*uefi.BIOSRegion":      func() Firmware { return &BIOSRegion{} },
	"*uefi.BIOSPadding":     func() Firmware { return &BIOSPadding{} },
	"*uefi.File":            func() Firmware { return &File{} },
	"*uefi.FirmwareVolume":  func() Firmware { return &FirmwareVolume{} },
	"*uefi.FlashDescriptor": func() Firmware { return &FlashDescriptor{} },
	"*uefi.FlashImage":      func() Firmware { return &FlashImage{} },
	"*uefi.MERegion":        func() Firmware { return &MERegion{} },
	"*uefi.RawRegion":       func() Firmware { return &RawRegion{} },
	"*uefi.Section":         func() Firmware { return &Section{} },
}

// MarshalFirmware marshals the firmware element to JSON, including the type information at the top.
func MarshalFirmware(f Firmware) ([]byte, error) {
	b, err := json.MarshalIndent(f, "", "    ")
	if err != nil {
		return nil, err
	}

	m := marshalFirmware{FType: reflect.TypeOf(f).String(), FirmwareElement: json.RawMessage(b)}
	return json.MarshalIndent(m, "", "    ")
}

// UnmarshalFirmware unmarshals the firmware element from JSON, using the type information at the top.
func UnmarshalFirmware(b []byte) (Firmware, error) {
	var m marshalFirmware
	if err := json.Unmarshal(b, &m); err != nil {
		return nil, err
	}
	factory, ok := firmwareTypes[m.FType]
	if !ok {
		return nil, fmt.Errorf("unknown Firmware type '%s', unable to unmarshal", m.FType)
	}
	f := factory()
	err := json.Unmarshal(m.FirmwareElement, &f)
	return f, err
}

// Parse exposes a high-level parser for generic firmware types. It does not
// implement any parser itself, but it calls known parsers that implement the
// Firmware interface.
func Parse(buf []byte) (Firmware, error) {
	if _, err := FindSignature(buf); err == nil {
		// Intel rom.
		return NewFlashImage(buf)
	}
	// Non intel image such as edk2's OVMF
	// We don't know how to parse this header, so treat it as a large BIOSRegion
	return NewBIOSRegion(buf, nil, RegionTypeBIOS)
}

// Checksum8 does a 8 bit checksum of the slice passed in.
func Checksum8(buf []byte) uint8 {
	var sum uint8
	for _, val := range buf {
		sum += val
	}
	return sum
}

// Checksum16 does a 16 bit checksum of the byte slice passed in.
func Checksum16(buf []byte) (uint16, error) {
	r := bytes.NewReader(buf)
	buflen := len(buf)
	if buflen%2 != 0 {
		return 0, fmt.Errorf("byte slice does not have even length, not able to do 16 bit checksum. Length was %v",
			buflen)
	}
	var temp, sum uint16
	for i := 0; i < buflen; i += 2 {
		if err := binary.Read(r, binary.LittleEndian, &temp); err != nil {
			return 0, err
		}
		sum += temp
	}
	return sum, nil
}

// Read3Size reads a 3-byte size and returns it as a uint64
func Read3Size(size [3]uint8) uint64 {
	return uint64(size[2])<<16 |
		uint64(size[1])<<8 | uint64(size[0])
}

// Write3Size writes a size into a 3-byte array
func Write3Size(size uint64) [3]uint8 {
	if size >= 0xFFFFFF {
		return [3]uint8{0xFF, 0xFF, 0xFF}
	}
	b := [3]uint8{uint8(size), uint8(size >> 8), uint8(size >> 16)}
	return b
}

// Align aligns an address
func Align(val uint64, base uint64) uint64 {
	return (val + base - 1) & ^(base - 1)
}

// Align4 aligns an address to 4 bytes
func Align4(val uint64) uint64 {
	return Align(val, 4)
}

// Align8 aligns an address to 8 bytes
func Align8(val uint64) uint64 {
	return Align(val, 8)
}

// Erase sets the buffer to be ErasePolarity
func Erase(buf []byte, polarity byte) {
	for j, blen := 0, len(buf); j < blen; j++ {
		buf[j] = Attributes.ErasePolarity
	}
}

// IsErased check if the buffer is ErasePolarity
func IsErased(buf []byte, polarity byte) bool {
	for _, c := range buf {
		if c != polarity {
			return false
		}
	}
	return true
}
<commit_msg>comment: Add an explicit warning about bricking a device<commit_after>// Copyright 2018 the LinuxBoot Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package uefi contents data types for the components found in UEFI and an
// Parse function for reading an image.
package uefi

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"reflect"
)

var (
	// ReadOnly breaks firmware modification operations, but optimizes
	// memory and CPU consumption for read-only operations.
	//
	// DO NOT USE THIS OPTION UNLESS YOU KNOW WHAT ARE YOU DOING. IF YOU
	// WILL MODIFY A FIRMWARE WITH THIS OPTION BEING ENABLED, THIS FIRMWARE
	// MIGHT BRICK YOUR DEVICE.
	ReadOnly = false

	// DisableDecompression disables section decompression.
	//
	// DO NOT USE THIS OPTION UNLESS YOU KNOW WHAT ARE YOU DOING. IF YOU
	// WILL MODIFY A FIRMWARE WITH THIS OPTION BEING ENABLED, THIS FIRMWARE
	// MIGHT BRICK YOUR DEVICE.
	DisableDecompression = false
)

// ROMAttributes is used to hold global variables that apply across the whole image.
// We have to do this to avoid passing too many things down each time.
type ROMAttributes struct {
	ErasePolarity byte // Either 0xFF or 0
}

const poisonedPolarity byte = 0xF0

// Attributes holds the global attributes
var Attributes = ROMAttributes{ErasePolarity: poisonedPolarity}

// SetErasePolarity sets the Erase Polarity for the flash image.
// It checks to see if there are conflicting Erase Polarities.
func SetErasePolarity(ep byte) error {
	if ep != 0xFF && ep != 0 {
		// Invalid erase polarity requested.
		return fmt.Errorf("invalid erase polarity requested, should only be 0x00 or 0xFF, got 0x%02X",
			ep)
	}
	// Set it only once.
	if Attributes.ErasePolarity != poisonedPolarity {
		// it's already been set. Check that they are the same.
		if Attributes.ErasePolarity != ep {
			return fmt.Errorf("conflicting erase polarities, was 0x%02X, requested 0x%02X",
				Attributes.ErasePolarity, ep)
		}
		return nil
	}
	Attributes.ErasePolarity = ep
	return nil
}

// Firmware is an interface to describe generic firmware types. When the
// firmware is parsed, all the Firmware objects are laid out in a tree (similar
// to an AST). This interface represents one node in said tree. The
// implementations (e.g. Flash image, or FirmwareVolume) must implement this
// interface.
type Firmware interface {
	Buf() []byte
	SetBuf(buf []byte)

	// Apply a visitor to the Firmware.
	Apply(v Visitor) error

	// Apply a visitor to all the direct children of the Firmware
	// (excluding the Firmware itself).
	ApplyChildren(v Visitor) error
}

// TypedFirmware includes the Firmware interface's type when exporting it to
// JSON. The type is required when unmarshalling.
type TypedFirmware struct {
	Type  string
	Value Firmware
}

// MakeTyped takes a Firmware interface and makes a (type, value) pair.
func MakeTyped(f Firmware) *TypedFirmware {
	return &TypedFirmware{
		Type:  reflect.TypeOf(f).String(),
		Value: f,
	}
}

// UnmarshalJSON unmarshals a TypedFirmware struct and correctly deduces the
// type of the interface.
func (f *TypedFirmware) UnmarshalJSON(b []byte) error {
	var getType struct {
		Type  string
		Value json.RawMessage
	}
	if err := json.Unmarshal(b, &getType); err != nil {
		return err
	}
	factory, ok := firmwareTypes[getType.Type]
	if !ok {
		return fmt.Errorf("unknown TypedFirmware type '%s', unable to unmarshal", getType.Type)
	}
	f.Type = getType.Type
	f.Value = factory()
	return json.Unmarshal(getType.Value, &f.Value)
}

// This should never be exposed, it is only used for marshalling different types to json.
type marshalFirmware struct {
	FType           string
	FirmwareElement json.RawMessage
}

var firmwareTypes = map[string]func() Firmware{
	"*uefi.BIOSRegion":      func() Firmware { return &BIOSRegion{} },
	"*uefi.BIOSPadding":     func() Firmware { return &BIOSPadding{} },
	"*uefi.File":            func() Firmware { return &File{} },
	"*uefi.FirmwareVolume":  func() Firmware { return &FirmwareVolume{} },
	"*uefi.FlashDescriptor": func() Firmware { return &FlashDescriptor{} },
	"*uefi.FlashImage":      func() Firmware { return &FlashImage{} },
	"*uefi.MERegion":        func() Firmware { return &MERegion{} },
	"*uefi.RawRegion":       func() Firmware { return &RawRegion{} },
	"*uefi.Section":         func() Firmware { return &Section{} },
}

// MarshalFirmware marshals the firmware element to JSON, including the type information at the top.
func MarshalFirmware(f Firmware) ([]byte, error) {
	b, err := json.MarshalIndent(f, "", "    ")
	if err != nil {
		return nil, err
	}

	m := marshalFirmware{FType: reflect.TypeOf(f).String(), FirmwareElement: json.RawMessage(b)}
	return json.MarshalIndent(m, "", "    ")
}

// UnmarshalFirmware unmarshals the firmware element from JSON, using the type information at the top.
func UnmarshalFirmware(b []byte) (Firmware, error) {
	var m marshalFirmware
	if err := json.Unmarshal(b, &m); err != nil {
		return nil, err
	}
	factory, ok := firmwareTypes[m.FType]
	if !ok {
		return nil, fmt.Errorf("unknown Firmware type '%s', unable to unmarshal", m.FType)
	}
	f := factory()
	err := json.Unmarshal(m.FirmwareElement, &f)
	return f, err
}

// Parse exposes a high-level parser for generic firmware types. It does not
// implement any parser itself, but it calls known parsers that implement the
// Firmware interface.
func Parse(buf []byte) (Firmware, error) {
	if _, err := FindSignature(buf); err == nil {
		// Intel rom.
		return NewFlashImage(buf)
	}
	// Non intel image such as edk2's OVMF
	// We don't know how to parse this header, so treat it as a large BIOSRegion
	return NewBIOSRegion(buf, nil, RegionTypeBIOS)
}

// Checksum8 does a 8 bit checksum of the slice passed in.
func Checksum8(buf []byte) uint8 {
	var sum uint8
	for _, val := range buf {
		sum += val
	}
	return sum
}

// Checksum16 does a 16 bit checksum of the byte slice passed in.
func Checksum16(buf []byte) (uint16, error) {
	r := bytes.NewReader(buf)
	buflen := len(buf)
	if buflen%2 != 0 {
		return 0, fmt.Errorf("byte slice does not have even length, not able to do 16 bit checksum. Length was %v",
			buflen)
	}
	var temp, sum uint16
	for i := 0; i < buflen; i += 2 {
		if err := binary.Read(r, binary.LittleEndian, &temp); err != nil {
			return 0, err
		}
		sum += temp
	}
	return sum, nil
}

// Read3Size reads a 3-byte size and returns it as a uint64
func Read3Size(size [3]uint8) uint64 {
	return uint64(size[2])<<16 |
		uint64(size[1])<<8 | uint64(size[0])
}

// Write3Size writes a size into a 3-byte array
func Write3Size(size uint64) [3]uint8 {
	if size >= 0xFFFFFF {
		return [3]uint8{0xFF, 0xFF, 0xFF}
	}
	b := [3]uint8{uint8(size), uint8(size >> 8), uint8(size >> 16)}
	return b
}

// Align aligns an address
func Align(val uint64, base uint64) uint64 {
	return (val + base - 1) & ^(base - 1)
}

// Align4 aligns an address to 4 bytes
func Align4(val uint64) uint64 {
	return Align(val, 4)
}

// Align8 aligns an address to 8 bytes
func Align8(val uint64) uint64 {
	return Align(val, 8)
}

// Erase sets the buffer to be ErasePolarity
func Erase(buf []byte, polarity byte) {
	for j, blen := 0, len(buf); j < blen; j++ {
		buf[j] = Attributes.ErasePolarity
	}
}

// IsErased check if the buffer is ErasePolarity
func IsErased(buf []byte, polarity byte) bool {
	for _, c := range buf {
		if c != polarity {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package util

import "path/filepath"

func ExpandPath(path string) string {
	res, err := filepath.Abs(path)
	if err != nil {
		panic(err) // stupid interface of filepath.Abs
	}
	return res
}
<commit_msg>[stapel image] Expand tilde to home directory for custom mount<commit_after>package util

import (
	"os/user"
	"path/filepath"
	"strings"
)

func ExpandPath(path string) string {
	var result string

	if path == "~" || strings.HasPrefix(path, "~/") {
		usr, err := user.Current()
		if err != nil {
			panic(err)
		}

		dir := usr.HomeDir

		if path == "~" {
			result = dir
		} else {
			result = filepath.Join(dir, path[2:])
		}
	} else {
		var err error
		result, err = filepath.Abs(path)
		if err != nil {
			panic(err) // stupid interface of filepath.Abs
		}
	}

	return result
}
<|endoftext|>


<commit_before>// Copyright Istio 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 webhooks

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"time"

	"k8s.io/api/admissionregistration/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	admissionregistrationv1beta1client "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1"
	"k8s.io/client-go/tools/cache"

	"istio.io/api/label"
	"istio.io/istio/pkg/kube"
	"istio.io/istio/pkg/queue"
	"istio.io/istio/pkg/webhooks/validation/controller"
	"istio.io/pkg/log"
)

var (
	errWrongRevision     = errors.New("webhook does not belong to target revision")
	errNoWebhookWithName = errors.New("webhook configuration did not contain webhook with target name")
)

// WebhookCertPatcher listens for webhooks on specified revision and patches their CA bundles
type WebhookCertPatcher struct {
	client kubernetes.Interface

	// revision to patch webhooks for
	revision    string
	webhookName string
	caCertPem   []byte

	queue queue.Instance
}

// Run runs the WebhookCertPatcher
func (w *WebhookCertPatcher) Run(stopChan <-chan struct{}) {
	go w.queue.Run(stopChan)
	go w.runWebhookController(stopChan)
}

// NewWebhookCertPatcher creates a WebhookCertPatcher
func NewWebhookCertPatcher(
	client kubernetes.Interface,
	revision, webhookName, caBundlePath string) (*WebhookCertPatcher, error) {

	// ca from k8s
	caCertPem, err := ioutil.ReadFile(caBundlePath)
	if err != nil {
		log.Errorf("Skipping webhook patch, missing CA path %v", caBundlePath)
		return nil, fmt.Errorf("could not read CA path: %v", err)
	}

	return &WebhookCertPatcher{
		client:      client,
		revision:    revision,
		webhookName: webhookName,
		caCertPem:   caCertPem,
		queue:       queue.NewQueue(time.Second * 2),
	}, nil
}

func (w *WebhookCertPatcher) runWebhookController(stopChan <-chan struct{}) {
	watchlist := cache.NewFilteredListWatchFromClient(
		w.client.AdmissionregistrationV1beta1().RESTClient(),
		"mutatingwebhookconfigurations",
		"",
		func(options *metav1.ListOptions) {
			options.LabelSelector = fmt.Sprintf("%s=%s", label.IstioRev, w.revision)
		})

	_, c := cache.NewInformer(
		watchlist,
		&v1beta1.MutatingWebhookConfiguration{},
		0,
		cache.ResourceEventHandlerFuncs{
			UpdateFunc: func(oldObj, newObj interface{}) {
				oldConfig := oldObj.(*v1beta1.MutatingWebhookConfiguration)
				newConfig := newObj.(*v1beta1.MutatingWebhookConfiguration)
				w.updateWebhookHandler(oldConfig, newConfig)
			},
			AddFunc: func(obj interface{}) {
				config := obj.(*v1beta1.MutatingWebhookConfiguration)
				w.addWebhookHandler(config)
			},
		},
	)

	c.Run(stopChan)
}

func (w *WebhookCertPatcher) updateWebhookHandler(oldConfig, newConfig *v1beta1.MutatingWebhookConfiguration) {
	if oldConfig.ResourceVersion != newConfig.ResourceVersion {
		for i, wh := range newConfig.Webhooks {
			if wh.Name == w.webhookName && !bytes.Equal(newConfig.Webhooks[i].ClientConfig.CABundle, w.caCertPem) {
				w.queue.Push(func() error {
					return w.webhookPatchTask(newConfig.Name)
				})
				break
			}
		}
	}
}

func (w *WebhookCertPatcher) addWebhookHandler(config *v1beta1.MutatingWebhookConfiguration) {
	for i, wh := range config.Webhooks {
		if wh.Name == w.webhookName && !bytes.Equal(config.Webhooks[i].ClientConfig.CABundle, w.caCertPem) {
			log.Infof("New webhook config added, patching MutatingWebhookConfiguration for %s", config.Name)
			w.queue.Push(func() error {
				return w.webhookPatchTask(config.Name)
			})
			break
		}
	}
}

// webhookPatchTask takes the result of patchMutatingWebhookConfig and modifies the result for use in task queue
func (w *WebhookCertPatcher) webhookPatchTask(webhookConfigName string) error {
	err := w.patchMutatingWebhookConfig(
		w.client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations(),
		webhookConfigName)

	// do not want to retry the task if these errors occur, they indicate that
	// we should no longer be patching the given webhook
	if errors.Is(err, errWrongRevision) || errors.Is(err, errNoWebhookWithName) {
		return nil
	}

	return err
}

// patchMutatingWebhookConfig takes a webhookConfigName and patches the CA bundle for that webhook configuration
func (w *WebhookCertPatcher) patchMutatingWebhookConfig(
	client admissionregistrationv1beta1client.MutatingWebhookConfigurationInterface,
	webhookConfigName string) error {
	config, err := client.Get(context.TODO(), webhookConfigName, metav1.GetOptions{})
	if err != nil {
		return err
	}
	// prevents a race condition between multiple istiods when the revision is changed or modified
	v, ok := config.Labels[label.IstioRev]
	if v != w.revision || !ok {
		return errWrongRevision
	}

	found := false
	for i, wh := range config.Webhooks {
		if wh.Name == w.webhookName {
			config.Webhooks[i].ClientConfig.CABundle = w.caCertPem
			found = true
		}
	}
	if !found {
		return errNoWebhookWithName
	}

	_, err = client.Update(context.TODO(), config, metav1.UpdateOptions{})
	return err
}

func CreateValidationWebhookController(client kube.Client,
	webhookConfigName, ns, caBundlePath string, remote bool) *controller.Controller {
	o := controller.Options{
		WatchedNamespace:    ns,
		CAPath:              caBundlePath,
		WebhookConfigName:   webhookConfigName,
		ServiceName:         "istiod",
		RemoteWebhookConfig: remote,
	}
	whController, err := controller.New(o, client)
	if err != nil {
		log.Errorf("failed to create validationWebhookController controller: %v", err)
	}
	return whController
}
<commit_msg>Bail out on webhook patch retries on NotFound error (#29647)<commit_after>// Copyright Istio 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 webhooks

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"time"

	"k8s.io/api/admissionregistration/v1beta1"
	kubeErrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	admissionregistrationv1beta1client "k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1"
	"k8s.io/client-go/tools/cache"

	"istio.io/api/label"
	"istio.io/istio/pkg/kube"
	"istio.io/istio/pkg/queue"
	"istio.io/istio/pkg/webhooks/validation/controller"
	"istio.io/pkg/log"
)

var (
	errWrongRevision     = errors.New("webhook does not belong to target revision")
	errNoWebhookWithName = errors.New("webhook configuration did not contain webhook with target name")
)

// WebhookCertPatcher listens for webhooks on specified revision and patches their CA bundles
type WebhookCertPatcher struct {
	client kubernetes.Interface

	// revision to patch webhooks for
	revision    string
	webhookName string
	caCertPem   []byte

	queue queue.Instance
}

// Run runs the WebhookCertPatcher
func (w *WebhookCertPatcher) Run(stopChan <-chan struct{}) {
	go w.queue.Run(stopChan)
	go w.runWebhookController(stopChan)
}

// NewWebhookCertPatcher creates a WebhookCertPatcher
func NewWebhookCertPatcher(
	client kubernetes.Interface,
	revision, webhookName, caBundlePath string) (*WebhookCertPatcher, error) {

	// ca from k8s
	caCertPem, err := ioutil.ReadFile(caBundlePath)
	if err != nil {
		log.Errorf("Skipping webhook patch, missing CA path %v", caBundlePath)
		return nil, fmt.Errorf("could not read CA path: %v", err)
	}

	return &WebhookCertPatcher{
		client:      client,
		revision:    revision,
		webhookName: webhookName,
		caCertPem:   caCertPem,
		queue:       queue.NewQueue(time.Second * 2),
	}, nil
}

func (w *WebhookCertPatcher) runWebhookController(stopChan <-chan struct{}) {
	watchlist := cache.NewFilteredListWatchFromClient(
		w.client.AdmissionregistrationV1beta1().RESTClient(),
		"mutatingwebhookconfigurations",
		"",
		func(options *metav1.ListOptions) {
			options.LabelSelector = fmt.Sprintf("%s=%s", label.IstioRev, w.revision)
		})

	_, c := cache.NewInformer(
		watchlist,
		&v1beta1.MutatingWebhookConfiguration{},
		0,
		cache.ResourceEventHandlerFuncs{
			UpdateFunc: func(oldObj, newObj interface{}) {
				oldConfig := oldObj.(*v1beta1.MutatingWebhookConfiguration)
				newConfig := newObj.(*v1beta1.MutatingWebhookConfiguration)
				w.updateWebhookHandler(oldConfig, newConfig)
			},
			AddFunc: func(obj interface{}) {
				config := obj.(*v1beta1.MutatingWebhookConfiguration)
				w.addWebhookHandler(config)
			},
		},
	)

	c.Run(stopChan)
}

func (w *WebhookCertPatcher) updateWebhookHandler(oldConfig, newConfig *v1beta1.MutatingWebhookConfiguration) {
	if oldConfig.ResourceVersion != newConfig.ResourceVersion {
		for i, wh := range newConfig.Webhooks {
			if wh.Name == w.webhookName && !bytes.Equal(newConfig.Webhooks[i].ClientConfig.CABundle, w.caCertPem) {
				w.queue.Push(func() error {
					return w.webhookPatchTask(newConfig.Name)
				})
				break
			}
		}
	}
}

func (w *WebhookCertPatcher) addWebhookHandler(config *v1beta1.MutatingWebhookConfiguration) {
	for i, wh := range config.Webhooks {
		if wh.Name == w.webhookName && !bytes.Equal(config.Webhooks[i].ClientConfig.CABundle, w.caCertPem) {
			log.Infof("New webhook config added, patching MutatingWebhookConfiguration for %s", config.Name)
			w.queue.Push(func() error {
				return w.webhookPatchTask(config.Name)
			})
			break
		}
	}
}

// webhookPatchTask takes the result of patchMutatingWebhookConfig and modifies the result for use in task queue
func (w *WebhookCertPatcher) webhookPatchTask(webhookConfigName string) error {
	err := w.patchMutatingWebhookConfig(
		w.client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations(),
		webhookConfigName)

	// do not want to retry the task if these errors occur, they indicate that
	// we should no longer be patching the given webhook
	if kubeErrors.IsNotFound(err) || errors.Is(err, errWrongRevision) || errors.Is(err, errNoWebhookWithName) {
		return nil
	}

	return err
}

// patchMutatingWebhookConfig takes a webhookConfigName and patches the CA bundle for that webhook configuration
func (w *WebhookCertPatcher) patchMutatingWebhookConfig(
	client admissionregistrationv1beta1client.MutatingWebhookConfigurationInterface,
	webhookConfigName string) error {
	config, err := client.Get(context.TODO(), webhookConfigName, metav1.GetOptions{})
	if err != nil {
		return err
	}
	// prevents a race condition between multiple istiods when the revision is changed or modified
	v, ok := config.Labels[label.IstioRev]
	if v != w.revision || !ok {
		return errWrongRevision
	}

	found := false
	for i, wh := range config.Webhooks {
		if wh.Name == w.webhookName {
			config.Webhooks[i].ClientConfig.CABundle = w.caCertPem
			found = true
		}
	}
	if !found {
		return errNoWebhookWithName
	}

	_, err = client.Update(context.TODO(), config, metav1.UpdateOptions{})
	return err
}

func CreateValidationWebhookController(client kube.Client,
	webhookConfigName, ns, caBundlePath string, remote bool) *controller.Controller {
	o := controller.Options{
		WatchedNamespace:    ns,
		CAPath:              caBundlePath,
		WebhookConfigName:   webhookConfigName,
		ServiceName:         "istiod",
		RemoteWebhookConfig: remote,
	}
	whController, err := controller.New(o, client)
	if err != nil {
		log.Errorf("failed to create validationWebhookController controller: %v", err)
	}
	return whController
}
<|endoftext|>