Merge pull request #773 from MartinForReal/shafan/boskos

Replace boskos client with the one in sigs.k8s.io/boskos
This commit is contained in:
Kubernetes Prow Robot
2023-08-03 20:30:21 -07:00
committed by GitHub
35 changed files with 1691 additions and 515 deletions

21
go.mod
View File

@@ -5,7 +5,7 @@ go 1.20
require (
cloud.google.com/go/compute/metadata v0.2.3
code.cloudfoundry.org/clock v1.1.0
contrib.go.opencensus.io/exporter/prometheus v0.0.0-20190427222117-f6cda26f80a3
contrib.go.opencensus.io/exporter/prometheus v0.1.0
contrib.go.opencensus.io/exporter/stackdriver v0.13.4
github.com/acobaugh/osrelease v0.1.0
github.com/avast/retry-go v3.0.0+incompatible
@@ -28,12 +28,12 @@ require (
golang.org/x/oauth2 v0.9.0
golang.org/x/sys v0.9.0
google.golang.org/api v0.114.0
k8s.io/api v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/api v0.17.3
k8s.io/apimachinery v0.17.3
k8s.io/client-go v11.0.1-0.20190805182717-6502b5e7b1b5+incompatible
k8s.io/component-base v0.17.2
k8s.io/klog v1.0.0
k8s.io/test-infra v0.0.0-20190914015041-e1cbc3ccd91c
sigs.k8s.io/boskos v0.0.0-20200515170311-7d36bde8cdf6
)
require (
@@ -54,14 +54,14 @@ require (
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/gofuzz v1.0.0 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
github.com/googleapis/gnostic v0.3.1 // indirect
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce // indirect
github.com/hashicorp/go-multierror v0.0.0-20171204182908-b7773ae21874 // indirect
github.com/imdario/mergo v0.3.7 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.0.0 // indirect
github.com/imdario/mergo v0.3.8 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
@@ -94,8 +94,9 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect
k8s.io/utils v0.0.0-20211116205334-6203023598ed // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
k8s.io/test-infra v0.0.0-20200514184223-ba32c8aae783 // indirect
k8s.io/utils v0.0.0-20200122174043-1e243dd1a584 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
replace (

636
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -27,7 +27,7 @@ import (
"k8s.io/node-problem-detector/pkg/util/tomb"
"k8s.io/node-problem-detector/test/e2e/lib/gce"
"k8s.io/test-infra/boskos/client"
"sigs.k8s.io/boskos/client"
"github.com/onsi/ginkgo"
"github.com/onsi/ginkgo/config"
@@ -101,7 +101,9 @@ func rentBoskosProjectIfNeededOnNode1() []byte {
}
fmt.Printf("Renting project from Boskos\n")
boskosClient = client.NewClient(*jobName, *boskosServerURL)
var err error
boskosClient, err = client.NewClient(*jobName, *boskosServerURL, "", "")
Expect(err).NotTo(HaveOccurred())
boskosRenewingTomb = tomb.NewTomb()
ctx, cancel := context.WithTimeout(context.Background(), *boskosWaitDuration)

View File

@@ -3,7 +3,7 @@ gofuzz
gofuzz is a library for populating go objects with random values.
[![GoDoc](https://godoc.org/github.com/google/gofuzz?status.png)](https://godoc.org/github.com/google/gofuzz)
[![GoDoc](https://godoc.org/github.com/google/gofuzz?status.svg)](https://godoc.org/github.com/google/gofuzz)
[![Travis](https://travis-ci.org/google/gofuzz.svg?branch=master)](https://travis-ci.org/google/gofuzz)
This is useful for testing:

View File

@@ -20,6 +20,7 @@ import (
"fmt"
"math/rand"
"reflect"
"regexp"
"time"
)
@@ -28,13 +29,14 @@ type fuzzFuncMap map[reflect.Type]reflect.Value
// Fuzzer knows how to fill any object with random fields.
type Fuzzer struct {
fuzzFuncs fuzzFuncMap
defaultFuzzFuncs fuzzFuncMap
r *rand.Rand
nilChance float64
minElements int
maxElements int
maxDepth int
fuzzFuncs fuzzFuncMap
defaultFuzzFuncs fuzzFuncMap
r *rand.Rand
nilChance float64
minElements int
maxElements int
maxDepth int
skipFieldPatterns []*regexp.Regexp
}
// New returns a new Fuzzer. Customize your Fuzzer further by calling Funcs,
@@ -150,6 +152,13 @@ func (f *Fuzzer) MaxDepth(d int) *Fuzzer {
return f
}
// Skip fields which match the supplied pattern. Call this multiple times if needed
// This is useful to skip XXX_ fields generated by protobuf
func (f *Fuzzer) SkipFieldsWithPattern(pattern *regexp.Regexp) *Fuzzer {
f.skipFieldPatterns = append(f.skipFieldPatterns, pattern)
return f
}
// Fuzz recursively fills all of obj's fields with something random. First
// this tries to find a custom fuzz function (see Funcs). If there is no
// custom function this tests whether the object implements fuzz.Interface and,
@@ -274,7 +283,17 @@ func (fc *fuzzerContext) doFuzz(v reflect.Value, flags uint64) {
v.Set(reflect.Zero(v.Type()))
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
fc.doFuzz(v.Field(i), 0)
skipField := false
fieldName := v.Type().Field(i).Name
for _, pattern := range fc.fuzzer.skipFieldPatterns {
if pattern.MatchString(fieldName) {
skipField = true
break
}
}
if !skipField {
fc.doFuzz(v.Field(i), 0)
}
}
case reflect.Chan:
fallthrough

View File

@@ -48,7 +48,7 @@ func main() {
// We can use the Contains helpers to check if an error contains
// another error. It is safe to do this with a nil error, or with
// an error that doesn't even use the errwrap package.
if errwrap.Contains(err, ErrNotExist) {
if errwrap.Contains(err, "does not exist") {
// Do something
}
if errwrap.ContainsType(err, new(os.PathError)) {

View File

@@ -13,7 +13,7 @@ type ErrorFormatFunc func([]error) string
// that occurred along with a bullet point list of the errors.
func ListFormatFunc(es []error) string {
if len(es) == 1 {
return fmt.Sprintf("1 error occurred:\n\n* %s", es[0])
return fmt.Sprintf("1 error occurred:\n\t* %s\n\n", es[0])
}
points := make([]string, len(es))
@@ -22,6 +22,6 @@ func ListFormatFunc(es []error) string {
}
return fmt.Sprintf(
"%d errors occurred:\n\n%s",
len(es), strings.Join(points, "\n"))
"%d errors occurred:\n\t%s\n\n",
len(es), strings.Join(points, "\n\t"))
}

16
vendor/github.com/hashicorp/go-multierror/sort.go generated vendored Normal file
View File

@@ -0,0 +1,16 @@
package multierror
// Len implements sort.Interface function for length
func (err Error) Len() int {
return len(err.Errors)
}
// Swap implements sort.Interface function for swapping elements
func (err Error) Swap(i, j int) {
err.Errors[i], err.Errors[j] = err.Errors[j], err.Errors[i]
}
// Less implements sort.Interface function for determining order
func (err Error) Less(i, j int) bool {
return err.Errors[i].Error() < err.Errors[j].Error()
}

View File

@@ -4,4 +4,6 @@ install:
- go get golang.org/x/tools/cmd/cover
- go get github.com/mattn/goveralls
script:
- go test -race -v ./...
after_script:
- $HOME/gopath/bin/goveralls -service=travis-ci -repotoken $COVERALLS_TOKEN

View File

@@ -26,10 +26,12 @@ func hasExportedField(dst reflect.Value) (exported bool) {
}
type Config struct {
Overwrite bool
AppendSlice bool
Transformers Transformers
overwriteWithEmptyValue bool
Overwrite bool
AppendSlice bool
TypeCheck bool
Transformers Transformers
overwriteWithEmptyValue bool
overwriteSliceWithEmptyValue bool
}
type Transformers interface {
@@ -41,7 +43,9 @@ type Transformers interface {
// short circuiting on recursive types.
func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, config *Config) (err error) {
overwrite := config.Overwrite
typeCheck := config.TypeCheck
overwriteWithEmptySrc := config.overwriteWithEmptyValue
overwriteSliceWithEmptySrc := config.overwriteSliceWithEmptyValue
config.overwriteWithEmptyValue = false
if !src.IsValid() {
@@ -128,11 +132,14 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
dstSlice = reflect.ValueOf(dstElement.Interface())
}
if (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
if typeCheck && srcSlice.Type() != dstSlice.Type() {
return fmt.Errorf("cannot override two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
}
dstSlice = srcSlice
} else if config.AppendSlice {
if srcSlice.Type() != dstSlice.Type() {
return fmt.Errorf("cannot append two slice with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
return fmt.Errorf("cannot append two slices with different type (%s, %s)", srcSlice.Type(), dstSlice.Type())
}
dstSlice = reflect.AppendSlice(dstSlice, srcSlice)
}
@@ -143,7 +150,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
continue
}
if srcElement.IsValid() && (overwrite || (!dstElement.IsValid() || isEmptyValue(dstElement))) {
if srcElement.IsValid() && ((srcElement.Kind() != reflect.Ptr && overwrite) || !dstElement.IsValid() || isEmptyValue(dstElement)) {
if dst.IsNil() {
dst.Set(reflect.MakeMap(dst.Type()))
}
@@ -154,7 +161,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
if !dst.CanSet() {
break
}
if (!isEmptyValue(src) || overwriteWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
if (!isEmptyValue(src) || overwriteWithEmptySrc || overwriteSliceWithEmptySrc) && (overwrite || isEmptyValue(dst)) && !config.AppendSlice {
dst.Set(src)
} else if config.AppendSlice {
if src.Type() != dst.Type() {
@@ -168,11 +175,21 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
if src.IsNil() {
break
}
if src.Kind() != reflect.Interface {
if dst.Kind() != reflect.Ptr && src.Type().AssignableTo(dst.Type()) {
if dst.IsNil() || overwrite {
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
dst.Set(src)
}
}
break
}
if src.Kind() != reflect.Interface {
if dst.IsNil() || (src.Kind() != reflect.Ptr && overwrite) {
if dst.CanSet() && (overwrite || isEmptyValue(dst)) {
dst.Set(src)
}
} else if src.Kind() == reflect.Ptr {
if err = deepMerge(dst.Elem(), src.Elem(), visited, depth+1, config); err != nil {
return
@@ -198,6 +215,7 @@ func deepMerge(dst, src reflect.Value, visited map[uintptr]*visit, depth int, co
dst.Set(src)
}
}
return
}
@@ -209,7 +227,7 @@ func Merge(dst, src interface{}, opts ...func(*Config)) error {
return merge(dst, src, opts...)
}
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overriden by
// MergeWithOverwrite will do the same as Merge except that non-empty dst attributes will be overridden by
// non-empty src attribute values.
// Deprecated: use Merge(…) with WithOverride
func MergeWithOverwrite(dst, src interface{}, opts ...func(*Config)) error {
@@ -228,11 +246,21 @@ func WithOverride(config *Config) {
config.Overwrite = true
}
// WithAppendSlice will make merge append slices instead of overwriting it
// WithOverride will make merge override empty dst slice with empty src slice.
func WithOverrideEmptySlice(config *Config) {
config.overwriteSliceWithEmptyValue = true
}
// WithAppendSlice will make merge append slices instead of overwriting it.
func WithAppendSlice(config *Config) {
config.AppendSlice = true
}
// WithTypeCheck will make merge check types while overwriting it (must be used with WithOverride).
func WithTypeCheck(config *Config) {
config.TypeCheck = true
}
func merge(dst, src interface{}, opts ...func(*Config)) error {
var (
vDst, vSrc reflect.Value

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"agent.go",
"secret.go",
],
importpath = "k8s.io/test-infra/prow/config/secret",
visibility = ["//visibility:public"],
deps = [
"//prow/logrusutil:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_k8s_apimachinery//pkg/util/sets:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["agent_test.go"],
embed = [":go_default_library"],
deps = [
"//prow/logrusutil:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
],
)

152
vendor/k8s.io/test-infra/prow/config/secret/agent.go generated vendored Normal file
View File

@@ -0,0 +1,152 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package secret implements an agent to read and reload the secrets.
package secret
import (
"bytes"
"os"
"sync"
"time"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/test-infra/prow/logrusutil"
)
// Agent watches a path and automatically loads the secrets stored.
type Agent struct {
sync.RWMutex
secretsMap map[string][]byte
}
// Start creates goroutines to monitor the files that contain the secret value.
// Additionally, Start wraps the current standard logger formatter with a
// censoring formatter that removes secret occurrences from the logs.
func (a *Agent) Start(paths []string) error {
secretsMap, err := LoadSecrets(paths)
if err != nil {
return err
}
a.secretsMap = secretsMap
// Start one goroutine for each file to monitor and update the secret's values.
for secretPath := range secretsMap {
go a.reloadSecret(secretPath)
}
logrus.SetFormatter(logrusutil.NewCensoringFormatter(logrus.StandardLogger().Formatter, a.getSecrets))
return nil
}
// Add registers a new path to the agent.
func (a *Agent) Add(path string) error {
secret, err := LoadSingleSecret(path)
if err != nil {
return err
}
a.setSecret(path, secret)
// Start one goroutine for each file to monitor and update the secret's values.
go a.reloadSecret(path)
return nil
}
// reloadSecret will begin polling the secret file at the path. If the first load
// fails, Start with return the error and abort. Future load failures will log
// the failure message but continue attempting to load.
func (a *Agent) reloadSecret(secretPath string) {
var lastModTime time.Time
logger := logrus.NewEntry(logrus.StandardLogger())
skips := 0
for range time.Tick(1 * time.Second) {
if skips < 600 {
// Check if the file changed to see if it needs to be re-read.
secretStat, err := os.Stat(secretPath)
if err != nil {
logger.WithField("secret-path", secretPath).
WithError(err).Error("Error loading secret file.")
continue
}
recentModTime := secretStat.ModTime()
if !recentModTime.After(lastModTime) {
skips++
continue // file hasn't been modified
}
lastModTime = recentModTime
}
if secretValue, err := LoadSingleSecret(secretPath); err != nil {
logger.WithField("secret-path: ", secretPath).
WithError(err).Error("Error loading secret.")
} else {
a.setSecret(secretPath, secretValue)
skips = 0
}
}
}
// GetSecret returns the value of a secret stored in a map.
func (a *Agent) GetSecret(secretPath string) []byte {
a.RLock()
defer a.RUnlock()
return a.secretsMap[secretPath]
}
// setSecret sets a value in a map of secrets.
func (a *Agent) setSecret(secretPath string, secretValue []byte) {
a.Lock()
defer a.Unlock()
a.secretsMap[secretPath] = secretValue
}
// GetTokenGenerator returns a function that gets the value of a given secret.
func (a *Agent) GetTokenGenerator(secretPath string) func() []byte {
return func() []byte {
return a.GetSecret(secretPath)
}
}
const censored = "CENSORED"
var censoredBytes = []byte(censored)
// Censor replaces sensitive parts of the content with a placeholder.
func (a *Agent) Censor(content []byte) []byte {
for sKey := range a.secretsMap {
secret := a.GetSecret(sKey)
content = bytes.ReplaceAll(content, secret, censoredBytes)
}
return content
}
func (a *Agent) getSecrets() sets.String {
a.RLock()
defer a.RUnlock()
secrets := sets.NewString()
for _, v := range a.secretsMap {
secrets.Insert(string(v))
}
return secrets
}

46
vendor/k8s.io/test-infra/prow/config/secret/secret.go generated vendored Normal file
View File

@@ -0,0 +1,46 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package secret
import (
"bytes"
"fmt"
"io/ioutil"
)
// LoadSecrets loads multiple paths of secrets and add them in a map.
func LoadSecrets(paths []string) (map[string][]byte, error) {
secretsMap := make(map[string][]byte, len(paths))
for _, path := range paths {
secretValue, err := LoadSingleSecret(path)
if err != nil {
return nil, err
}
secretsMap[path] = secretValue
}
return secretsMap, nil
}
// LoadSingleSecret reads and returns the value of a single file.
func LoadSingleSecret(path string) ([]byte, error) {
b, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("error reading %s: %v", path, err)
}
return bytes.TrimSpace(b), nil
}

37
vendor/k8s.io/test-infra/prow/logrusutil/BUILD.bazel generated vendored Normal file
View File

@@ -0,0 +1,37 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["logrusutil.go"],
importpath = "k8s.io/test-infra/prow/logrusutil",
visibility = ["//visibility:public"],
deps = [
"//prow/version:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_k8s_apimachinery//pkg/util/sets:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["logrusutil_test.go"],
embed = [":go_default_library"],
deps = [
"@com_github_sirupsen_logrus//:go_default_library",
"@io_k8s_apimachinery//pkg/util/sets:go_default_library",
],
)

129
vendor/k8s.io/test-infra/prow/logrusutil/logrusutil.go generated vendored Normal file
View File

@@ -0,0 +1,129 @@
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package logrusutil implements some helpers for using logrus
package logrusutil
import (
"bytes"
"strings"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/test-infra/prow/version"
)
// DefaultFieldsFormatter wraps another logrus.Formatter, injecting
// DefaultFields into each Format() call, existing fields are preserved
// if they have the same key
type DefaultFieldsFormatter struct {
WrappedFormatter logrus.Formatter
DefaultFields logrus.Fields
PrintLineNumber bool
}
// Init set Logrus formatter
// if DefaultFieldsFormatter.wrappedFormatter is nil &logrus.JSONFormatter{} will be used instead
func Init(formatter *DefaultFieldsFormatter) {
if formatter == nil {
return
}
if formatter.WrappedFormatter == nil {
formatter.WrappedFormatter = &logrus.JSONFormatter{}
}
logrus.SetFormatter(formatter)
logrus.SetReportCaller(formatter.PrintLineNumber)
}
// ComponentInit is a syntax sugar for easier Init
func ComponentInit() {
Init(
&DefaultFieldsFormatter{
PrintLineNumber: true,
DefaultFields: logrus.Fields{"component": version.Name},
},
)
}
// Format implements logrus.Formatter's Format. We allocate a new Fields
// map in order to not modify the caller's Entry, as that is not a thread
// safe operation.
func (f *DefaultFieldsFormatter) Format(entry *logrus.Entry) ([]byte, error) {
data := make(logrus.Fields, len(entry.Data)+len(f.DefaultFields))
for k, v := range f.DefaultFields {
data[k] = v
}
for k, v := range entry.Data {
data[k] = v
}
return f.WrappedFormatter.Format(&logrus.Entry{
Logger: entry.Logger,
Data: data,
Time: entry.Time,
Level: entry.Level,
Message: entry.Message,
Caller: entry.Caller,
})
}
// CensoringFormatter represents a logrus formatter that
// can be used to censor sensitive information
type CensoringFormatter struct {
delegate logrus.Formatter
getSecrets func() sets.String
}
func (f CensoringFormatter) Format(entry *logrus.Entry) ([]byte, error) {
raw, err := f.delegate.Format(entry)
if err != nil {
return raw, err
}
return f.censor(raw), nil
}
const censored = "CENSORED"
var (
censoredBytes = []byte(censored)
standardLog = logrus.NewEntry(logrus.StandardLogger())
)
// Censor replaces sensitive parts of the content with a placeholder.
func (f CensoringFormatter) censor(content []byte) []byte {
for _, secret := range f.getSecrets().List() {
trimmedSecret := strings.TrimSpace(secret)
if trimmedSecret != secret {
standardLog.Warning("Secret is not trimmed")
secret = trimmedSecret
}
if secret == "" {
standardLog.Warning("Secret is an empty string, ignoring")
continue
}
content = bytes.ReplaceAll(content, []byte(secret), censoredBytes)
}
return content
}
// NewCensoringFormatter generates a `CensoringFormatter` with
// a formatter as delegate and a set of strings to censor
func NewCensoringFormatter(f logrus.Formatter, getSecrets func() sets.String) CensoringFormatter {
return CensoringFormatter{
getSecrets: getSecrets,
delegate: f,
}
}

View File

@@ -2,10 +2,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/test-infra/boskos/storage",
srcs = ["doc.go"],
importpath = "k8s.io/test-infra/prow/version",
visibility = ["//visibility:public"],
deps = ["//boskos/common:go_default_library"],
x_defs = {"Version": "{DOCKER_TAG}"},
)
filegroup(

36
vendor/k8s.io/test-infra/prow/version/doc.go generated vendored Normal file
View File

@@ -0,0 +1,36 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// version holds variables that identify a Prow binary's name and version
package version
var (
// Name is the colloquial identifier for the compiled component
Name = "unset"
// Version is a concatenation of the commit SHA and date for the build
Version = "0"
)
// UserAgent exposes the component's name and version for user-agent header
func UserAgent() string {
return Name + "/" + Version
}
// UserAgentFor exposes the component's name and version for user-agent header
// while embedding the additional identifier
func UserAgentWithIdentifier(identifier string) string {
return Name + "." + identifier + "/" + Version
}

View File

@@ -1,45 +0,0 @@
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package inotify // import "k8s.io/utils/inotify"
import (
"sync"
)
// Event represents a notification
type Event struct {
Mask uint32 // Mask of events
Cookie uint32 // Unique cookie associating related events (for rename(2))
Name string // File name (optional)
}
type watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}
// Watcher represents an inotify instance
type Watcher struct {
mu sync.Mutex
fd int // File descriptor (as returned by the inotify_init() syscall)
watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor)
Error chan error // Errors are sent on this channel
Event chan *Event // Events are returned on this channel
done chan bool // Channel for sending a "quit message" to the reader goroutine
isClosed bool // Set to true when Close() is first called
}

View File

@@ -1,5 +1,3 @@
// +build linux
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@@ -33,10 +31,35 @@ import (
"fmt"
"os"
"strings"
"sync"
"syscall"
"unsafe"
)
// Event represents a notification
type Event struct {
Mask uint32 // Mask of events
Cookie uint32 // Unique cookie associating related events (for rename(2))
Name string // File name (optional)
}
type watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}
// Watcher represents an inotify instance
type Watcher struct {
mu sync.Mutex
fd int // File descriptor (as returned by the inotify_init() syscall)
watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor)
Error chan error // Errors are sent on this channel
Event chan *Event // Events are returned on this channel
done chan bool // Channel for sending a "quit message" to the reader goroutine
isClosed bool // Set to true when Close() is first called
}
// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
fd, errno := syscall.InotifyInit1(syscall.IN_CLOEXEC)
@@ -120,11 +143,7 @@ func (w *Watcher) RemoveWatch(path string) error {
}
success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
if success == -1 {
// when file descriptor or watch descriptor not found, InotifyRmWatch syscall return EINVAL error
// if return error, it may lead this path remain in watches and paths map, and no other event can trigger remove action.
if errno != syscall.EINVAL {
return os.NewSyscallError("inotify_rm_watch", errno)
}
return os.NewSyscallError("inotify_rm_watch", errno)
}
delete(w.watches, path)
// Locking here to protect the read from paths in readEvents.

View File

@@ -1,54 +0,0 @@
// +build !linux
/*
Copyright 2020 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package inotify // import "k8s.io/utils/inotify"
import (
"fmt"
"runtime"
)
var errNotSupported = fmt.Errorf("watch not supported on %s", runtime.GOOS)
// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
return nil, errNotSupported
}
// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
return errNotSupported
}
// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) AddWatch(path string, flags uint32) error {
return errNotSupported
}
// Watch adds path to the watched file set, watching all events.
func (w *Watcher) Watch(path string) error {
return errNotSupported
}
// RemoveWatch removes path from the watched file set.
func (w *Watcher) RemoveWatch(path string) error {
return errNotSupported
}

33
vendor/modules.txt vendored
View File

@@ -23,7 +23,7 @@ cloud.google.com/go/trace/internal
## explicit; go 1.20
code.cloudfoundry.org/clock
code.cloudfoundry.org/clock/fakeclock
# contrib.go.opencensus.io/exporter/prometheus v0.0.0-20190427222117-f6cda26f80a3
# contrib.go.opencensus.io/exporter/prometheus v0.1.0
## explicit
contrib.go.opencensus.io/exporter/prometheus
# contrib.go.opencensus.io/exporter/stackdriver v0.13.4
@@ -140,7 +140,7 @@ github.com/google/go-cmp/cmp/internal/diff
github.com/google/go-cmp/cmp/internal/flags
github.com/google/go-cmp/cmp/internal/function
github.com/google/go-cmp/cmp/internal/value
# github.com/google/gofuzz v1.0.0
# github.com/google/gofuzz v1.1.0
## explicit; go 1.12
github.com/google/gofuzz
# github.com/google/uuid v1.3.0
@@ -161,10 +161,10 @@ github.com/googleapis/gax-go/v2/internal
github.com/googleapis/gnostic/OpenAPIv2
github.com/googleapis/gnostic/compiler
github.com/googleapis/gnostic/extensions
# github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce
# github.com/hashicorp/errwrap v1.0.0
## explicit
github.com/hashicorp/errwrap
# github.com/hashicorp/go-multierror v0.0.0-20171204182908-b7773ae21874
# github.com/hashicorp/go-multierror v1.0.0
## explicit
github.com/hashicorp/go-multierror
# github.com/hpcloud/tail v1.0.0
@@ -174,7 +174,7 @@ github.com/hpcloud/tail/ratelimiter
github.com/hpcloud/tail/util
github.com/hpcloud/tail/watch
github.com/hpcloud/tail/winfile
# github.com/imdario/mergo v0.3.7
# github.com/imdario/mergo v0.3.8
## explicit
github.com/imdario/mergo
# github.com/jmespath/go-jmespath v0.4.0
@@ -564,7 +564,7 @@ gopkg.in/yaml.v2
# gopkg.in/yaml.v3 v3.0.1
## explicit
gopkg.in/yaml.v3
# k8s.io/api v0.17.2 => k8s.io/api v0.17.2
# k8s.io/api v0.17.3 => k8s.io/api v0.17.2
## explicit; go 1.12
k8s.io/api/admissionregistration/v1
k8s.io/api/admissionregistration/v1beta1
@@ -606,7 +606,7 @@ k8s.io/api/settings/v1alpha1
k8s.io/api/storage/v1
k8s.io/api/storage/v1alpha1
k8s.io/api/storage/v1beta1
# k8s.io/apimachinery v0.17.2 => k8s.io/apimachinery v0.17.2
# k8s.io/apimachinery v0.17.3 => k8s.io/apimachinery v0.17.2
## explicit; go 1.12
k8s.io/apimachinery/pkg/api/errors
k8s.io/apimachinery/pkg/api/meta
@@ -724,17 +724,22 @@ k8s.io/klog
# k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a
## explicit; go 1.12
k8s.io/kube-openapi/pkg/util/proto
# k8s.io/test-infra v0.0.0-20190914015041-e1cbc3ccd91c
# k8s.io/test-infra v0.0.0-20200514184223-ba32c8aae783
## explicit; go 1.13
k8s.io/test-infra/boskos/client
k8s.io/test-infra/boskos/common
k8s.io/test-infra/boskos/storage
# k8s.io/utils v0.0.0-20211116205334-6203023598ed
k8s.io/test-infra/prow/config/secret
k8s.io/test-infra/prow/logrusutil
k8s.io/test-infra/prow/version
# k8s.io/utils v0.0.0-20200122174043-1e243dd1a584
## explicit; go 1.12
k8s.io/utils/inotify
k8s.io/utils/integer
# sigs.k8s.io/yaml v1.1.0
## explicit
# sigs.k8s.io/boskos v0.0.0-20200515170311-7d36bde8cdf6
## explicit; go 1.14
sigs.k8s.io/boskos/client
sigs.k8s.io/boskos/common
sigs.k8s.io/boskos/storage
# sigs.k8s.io/yaml v1.2.0
## explicit; go 1.12
sigs.k8s.io/yaml
# k8s.io/api => k8s.io/api v0.17.2
# k8s.io/apimachinery => k8s.io/apimachinery v0.17.2

201
vendor/sigs.k8s.io/boskos/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

View File

@@ -20,9 +20,11 @@ go_library(
deps = [
"//boskos/common:go_default_library",
"//boskos/storage:go_default_library",
"//prow/config/secret:go_default_library",
"@com_github_google_uuid//:go_default_library",
"@com_github_hashicorp_go_multierror//:go_default_library",
"@com_github_sirupsen_logrus//:go_default_library",
"@io_k8s_apimachinery//pkg/util/errors:go_default_library",
],
)

View File

@@ -33,12 +33,14 @@ import (
"syscall"
"time"
multierror "github.com/hashicorp/go-multierror"
"github.com/google/uuid"
"github.com/hashicorp/go-multierror"
"github.com/sirupsen/logrus"
"k8s.io/test-infra/boskos/common"
"k8s.io/test-infra/boskos/storage"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/test-infra/prow/config/secret"
"sigs.k8s.io/boskos/common"
"sigs.k8s.io/boskos/storage"
)
var (
@@ -60,9 +62,11 @@ type Client struct {
// http is the http.Client used to interact with the boskos REST API
http http.Client
owner string
url string
lock sync.Mutex
owner string
url string
username string
getPassword func() []byte
lock sync.Mutex
storage storage.PersistenceLayer
}
@@ -71,12 +75,37 @@ type Client struct {
//
// Clients created with this function default to retrying failed connection
// attempts three times with a ten second pause between each attempt.
func NewClient(owner string, url string) *Client {
func NewClient(owner string, urlString, username, passwordFile string) (*Client, error) {
if (username == "") != (passwordFile == "") {
return nil, fmt.Errorf("username and passwordFile must be specified together")
}
var getPassword func() []byte
if passwordFile != "" {
u, err := url.Parse(urlString)
if err != nil {
return nil, err
}
if u.Scheme != "https" {
// returning error here would make the tests hard
// we print out a warning message here instead
fmt.Printf("[WARNING] should NOT use password without enabling TLS: '%s'\n", urlString)
}
sa := &secret.Agent{}
if err := sa.Start([]string{passwordFile}); err != nil {
logrus.WithError(err).Fatal("Failed to start secrets agent")
}
getPassword = sa.GetTokenGenerator(passwordFile)
}
client := &Client{
url: url,
owner: owner,
storage: storage.NewMemoryStorage(),
url: urlString,
username: username,
getPassword: getPassword,
owner: owner,
storage: storage.NewMemoryStorage(),
}
// Configure the dialer to attempt three additional times to establish
@@ -104,7 +133,7 @@ func NewClient(owner string, url string) *Client {
ExpectContinueTimeout: 1 * time.Second,
}
return client
return client, nil
}
// public method
@@ -220,8 +249,8 @@ func (c *Client) ReleaseAll(dest string) error {
}
var allErrors error
for _, r := range resources {
c.storage.Delete(r.GetName())
err := c.Release(r.GetName(), dest)
c.storage.Delete(r.Name)
err := c.Release(r.Name, dest)
if err != nil {
allErrors = multierror.Append(allErrors, err)
}
@@ -258,7 +287,7 @@ func (c *Client) UpdateAll(state string) error {
}
var allErrors error
for _, r := range resources {
if err := c.Update(r.GetName(), state, nil); err != nil {
if err := c.Update(r.Name, state, nil); err != nil {
allErrors = multierror.Append(allErrors, err)
continue
}
@@ -283,12 +312,7 @@ func (c *Client) SyncAll() error {
return nil
}
var allErrors error
for _, i := range resources {
r, err := common.ItemToResource(i)
if err != nil {
allErrors = multierror.Append(allErrors, err)
continue
}
for _, r := range resources {
if err := c.Update(r.Name, r.State, nil); err != nil {
allErrors = multierror.Append(allErrors, err)
continue
@@ -309,7 +333,7 @@ func (c *Client) UpdateOne(name, state string, userData *common.UserData) error
if err != nil {
return fmt.Errorf("no resource name %v", name)
}
if err := c.Update(r.GetName(), state, userData); err != nil {
if err := c.Update(r.Name, state, userData); err != nil {
return err
}
return c.updateLocalResource(r, state, userData)
@@ -335,18 +359,14 @@ func (c *Client) HasResource() bool {
// private methods
func (c *Client) updateLocalResource(i common.Item, state string, data *common.UserData) error {
res, err := common.ItemToResource(i)
if err != nil {
return err
}
func (c *Client) updateLocalResource(res common.Resource, state string, data *common.UserData) error {
res.State = state
if res.UserData == nil {
res.UserData = data
} else {
res.UserData.Update(data)
}
_, err = c.storage.Update(res)
_, err := c.storage.Update(res)
return err
}
@@ -359,34 +379,45 @@ func (c *Client) acquire(rtype, state, dest, requestID string) (*common.Resource
if requestID != "" {
values.Set("request_id", requestID)
}
resp, err := c.httpPost("/acquire", values, "", nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
res := common.Resource{}
res := common.Resource{}
err = json.Unmarshal(body, &res)
work := func(retriedErrs *[]error) (bool, error) {
resp, err := c.httpPost("/acquire", values, "", nil)
if err != nil {
return nil, err
// Swallow the error so we can retry
*retriedErrs = append(*retriedErrs, err)
return false, nil
}
if res.Name == "" {
return nil, fmt.Errorf("unable to parse resource")
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
err = json.Unmarshal(body, &res)
if err != nil {
return false, err
}
if res.Name == "" {
return false, fmt.Errorf("unable to parse resource")
}
return true, nil
case http.StatusUnauthorized:
return false, ErrAlreadyInUse
case http.StatusNotFound:
return false, ErrNotFound
default:
*retriedErrs = append(*retriedErrs, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode))
// Swallow it so we can retry
return false, nil
}
return &res, nil
case http.StatusUnauthorized:
return nil, ErrAlreadyInUse
case http.StatusNotFound:
return nil, ErrNotFound
}
return nil, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode)
return &res, retry(work)
}
func (c *Client) acquireByState(state, dest string, names []string) ([]common.Resource, error) {
@@ -395,25 +426,33 @@ func (c *Client) acquireByState(state, dest string, names []string) ([]common.Re
values.Set("dest", dest)
values.Set("names", strings.Join(names, ","))
values.Set("owner", c.owner)
resp, err := c.httpPost("/acquirebystate", values, "", nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var resources []common.Resource
switch resp.StatusCode {
case http.StatusOK:
var resources []common.Resource
if err := json.NewDecoder(resp.Body).Decode(&resources); err != nil {
return nil, err
work := func(retriedErrs *[]error) (bool, error) {
resp, err := c.httpPost("/acquirebystate", values, "", nil)
if err != nil {
*retriedErrs = append(*retriedErrs, err)
return false, nil
}
defer resp.Body.Close()
switch resp.StatusCode {
case http.StatusOK:
if err := json.NewDecoder(resp.Body).Decode(&resources); err != nil {
return false, err
}
return true, nil
case http.StatusUnauthorized:
return false, ErrAlreadyInUse
case http.StatusNotFound:
return false, ErrNotFound
default:
*retriedErrs = append(*retriedErrs, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode))
return false, nil
}
return resources, nil
case http.StatusUnauthorized:
return nil, ErrAlreadyInUse
case http.StatusNotFound:
return nil, ErrNotFound
}
return nil, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode)
return resources, retry(work)
}
// Release a lease for a resource and set its state to the destination state
@@ -422,43 +461,62 @@ func (c *Client) Release(name, dest string) error {
values.Set("name", name)
values.Set("dest", dest)
values.Set("owner", c.owner)
resp, err := c.httpPost("/release", values, "", nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status %s, statusCode %v releasing %s", resp.Status, resp.StatusCode, name)
work := func(retriedErrs *[]error) (bool, error) {
resp, err := c.httpPost("/release", values, "", nil)
if err != nil {
*retriedErrs = append(*retriedErrs, err)
return false, nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
*retriedErrs = append(*retriedErrs, fmt.Errorf("status %s, statusCode %v releasing %s", resp.Status, resp.StatusCode, name))
return false, nil
}
return true, nil
}
return nil
return retry(work)
}
// Update a resource on the server, setting the state and user data
func (c *Client) Update(name, state string, userData *common.UserData) error {
var body io.Reader
var bodyData *bytes.Buffer
if userData != nil {
b := new(bytes.Buffer)
err := json.NewEncoder(b).Encode(userData)
bodyData = new(bytes.Buffer)
err := json.NewEncoder(bodyData).Encode(userData)
if err != nil {
return err
}
body = b
}
values := url.Values{}
values.Set("name", name)
values.Set("owner", c.owner)
values.Set("state", state)
resp, err := c.httpPost("/update", values, "application/json", body)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("status %s, status code %v updating %s", resp.Status, resp.StatusCode, name)
work := func(retriedErrs *[]error) (bool, error) {
// As the body is an io.Reader and hence its content
// can only be read once, we have to copy it for every request we make
var body io.Reader
if bodyData != nil {
body = bytes.NewReader(bodyData.Bytes())
}
resp, err := c.httpPost("/update", values, "application/json", body)
if err != nil {
*retriedErrs = append(*retriedErrs, err)
return false, nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
*retriedErrs = append(*retriedErrs, fmt.Errorf("status %s, status code %v updating %s", resp.Status, resp.StatusCode, name))
return false, nil
}
return true, nil
}
return nil
return retry(work)
}
func (c *Client) reset(rtype, state string, expire time.Duration, dest string) (map[string]string, error) {
@@ -468,46 +526,59 @@ func (c *Client) reset(rtype, state string, expire time.Duration, dest string) (
values.Set("state", state)
values.Set("expire", expire.String())
values.Set("dest", dest)
resp, err := c.httpPost("/reset", values, "", nil)
if err != nil {
return rmap, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
work := func(retriedErrs *[]error) (bool, error) {
resp, err := c.httpPost("/reset", values, "", nil)
if err != nil {
return rmap, err
*retriedErrs = append(*retriedErrs, err)
return false, nil
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
err = json.Unmarshal(body, &rmap)
return true, err
}
*retriedErrs = append(*retriedErrs, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode))
return false, nil
err = json.Unmarshal(body, &rmap)
return rmap, err
}
return rmap, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode)
return rmap, retry(work)
}
func (c *Client) metric(rtype string) (common.Metric, error) {
var metric common.Metric
values := url.Values{}
values.Set("type", rtype)
resp, err := c.httpGet("/metric", values)
if err != nil {
return metric, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return metric, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode)
work := func(retriedErrs *[]error) (bool, error) {
resp, err := c.httpGet("/metric", values)
if err != nil {
*retriedErrs = append(*retriedErrs, err)
return false, nil
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
*retriedErrs = append(*retriedErrs, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode))
return false, nil
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
return true, json.Unmarshal(body, &metric)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return metric, err
}
err = json.Unmarshal(body, &metric)
return metric, err
return metric, retry(work)
}
func (c *Client) httpGet(action string, values url.Values) (*http.Response, error) {
@@ -518,6 +589,9 @@ func (c *Client) httpGet(action string, values url.Values) (*http.Response, erro
if err != nil {
return nil, err
}
if c.username != "" && c.getPassword != nil {
req.SetBasicAuth(c.username, string(c.getPassword()))
}
return c.http.Do(req)
}
@@ -529,6 +603,9 @@ func (c *Client) httpPost(action string, values url.Values, contentType string,
if err != nil {
return nil, err
}
if c.username != "" && c.getPassword != nil {
req.SetBasicAuth(c.username, string(c.getPassword()))
}
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
@@ -606,3 +683,34 @@ func isDialErrorRetriable(err error) bool {
}
return false
}
// workFunc describes retrieable work. It should
// * Return an error for non-recoverable errors
// * Write retriable errors into `retriedErrs` and return with false, nil
// * Return with true, nil on success
type workFunc func(retriedErrs *[]error) (bool, error)
// SleepFunc is called when requests are retried. This may be replaced in tests.
var SleepFunc = time.Sleep
func retry(work workFunc) error {
var retriedErrs []error
maxAttempts := 4
for i := 1; i <= maxAttempts; i++ {
success, err := work(&retriedErrs)
if err != nil {
return err
}
if success {
return nil
}
if i == maxAttempts {
break
}
SleepFunc(time.Duration(i*i) * time.Second)
}
return utilerrors.NewAggregate(retriedErrs)
}

View File

@@ -41,6 +41,5 @@ filegroup(
go_test(
name = "go_default_test",
srcs = ["common_test.go"],
data = ["//boskos:testdata"],
embed = [":go_default_library"],
)

View File

@@ -30,12 +30,12 @@ import (
const (
// Busy state defines a resource being used.
Busy = "busy"
// Cleaning state defines a resource being cleaned
Cleaning = "cleaning"
// Dirty state defines a resource that needs cleaning
Dirty = "dirty"
// Free state defines a resource that is usable
Free = "free"
// Cleaning state defines a resource being cleaned
Cleaning = "cleaning"
// Leased state defines a resource being leased in order to make a new resource
Leased = "leased"
// ToBeDeleted is used for resources about to be deleted, they will be verified by a cleaner which mark them as tombstone
@@ -46,6 +46,19 @@ const (
Other = "other"
)
var (
// KnownStates is the set of all known states, excluding "other".
KnownStates = []string{
Busy,
Cleaning,
Dirty,
Free,
Leased,
ToBeDeleted,
Tombstone,
}
)
// UserData is a map of Name to user defined interface, serialized into a string
type UserData struct {
sync.Map
@@ -57,11 +70,6 @@ type UserDataMap map[string]string
// LeasedResources is a list of resources name that used in order to create another resource by Mason
type LeasedResources []string
// Item interfaces for resources and configs
type Item interface {
GetName() string
}
// Duration is a wrapper around time.Duration that parses times in either
// 'integer number of nanoseconds' or 'duration string' formats and serializes
// to 'duration string' format.
@@ -133,9 +141,13 @@ type Metric struct {
// TODO: implements state transition metrics
}
// IsInUse reports if the resource is owned by anything else than Boskos.
func (r *Resource) IsInUse() bool {
return r.Owner != ""
// NewMetric returns a new Metric struct.
func NewMetric(rtype string) Metric {
return Metric{
Type: rtype,
Current: map[string]int{},
Owners: map[string]int{},
}
}
// NewResource creates a new Boskos Resource.
@@ -150,7 +162,6 @@ func NewResource(name, rtype, state, owner string, t time.Time) Resource {
State: state,
Owner: owner,
LastUpdate: t,
UserData: &UserData{},
}
}
@@ -181,42 +192,12 @@ func (ud *UserDataNotFound) Error() string {
return fmt.Sprintf("user data ID %s does not exist", ud.ID)
}
// ResourceByUpdateTime helps sorting resources by update time
type ResourceByUpdateTime []Resource
func (ut ResourceByUpdateTime) Len() int { return len(ut) }
func (ut ResourceByUpdateTime) Swap(i, j int) { ut[i], ut[j] = ut[j], ut[i] }
func (ut ResourceByUpdateTime) Less(i, j int) bool { return ut[i].LastUpdate.Before(ut[j].LastUpdate) }
// ResourceByName helps sorting resources by name
type ResourceByName []Resource
func (ut ResourceByName) Len() int { return len(ut) }
func (ut ResourceByName) Swap(i, j int) { ut[i], ut[j] = ut[j], ut[i] }
func (ut ResourceByName) Less(i, j int) bool { return ut[i].GetName() < ut[j].GetName() }
// ResourceByDeleteState helps sorting resources by state, putting Tombstone first, then ToBeDeleted,
// and sorting alphabetacally by resource name
type ResourceByDeleteState []Resource
func (ut ResourceByDeleteState) Len() int { return len(ut) }
func (ut ResourceByDeleteState) Swap(i, j int) { ut[i], ut[j] = ut[j], ut[i] }
func (ut ResourceByDeleteState) Less(i, j int) bool {
order := map[string]int{Tombstone: 0, ToBeDeleted: 1}
stateIndex := func(s string) int {
i, ok := order[s]
if ok {
return i
}
return 2
}
indexI := stateIndex(ut[i].State)
indexJ := stateIndex(ut[i].State)
if indexI == indexJ {
return ut[i].GetName() < ut[j].GetName()
}
return indexI < indexJ
}
func (ut ResourceByName) Less(i, j int) bool { return ut[i].Name < ut[j].Name }
// CommaSeparatedStrings is used to parse comma separated string flag into a list of strings
type CommaSeparatedStrings []string
@@ -240,9 +221,6 @@ func (r *CommaSeparatedStrings) Type() string {
return "commaSeparatedStrings"
}
// GetName implements the Item interface used for storage
func (res Resource) GetName() string { return res.Name }
// UnmarshalJSON implements JSON Unmarshaler interface
func (ud *UserData) UnmarshalJSON(data []byte) error {
tmpMap := UserDataMap{}
@@ -313,12 +291,3 @@ func (ud *UserData) FromMap(m UserDataMap) {
ud.Store(key, value)
}
}
// ItemToResource casts a Item back to a Resource
func ItemToResource(i Item) (Resource, error) {
res, ok := i.(Resource)
if !ok {
return Resource{}, fmt.Errorf("cannot construct Resource from received object %v", i)
}
return res, nil
}

View File

@@ -17,7 +17,6 @@ limitations under the License.
package common
import (
"fmt"
"time"
"github.com/google/uuid"
@@ -43,9 +42,12 @@ type DynamicResourceLifeCycle struct {
Type string `json:"type"`
// Initial state to be created as
InitialState string `json:"state"`
// Minimum Number of resources to be use a buffer
// Minimum number of resources to be use as a buffer.
// Resources in the process of being deleted and cleaned up are included in this count.
MinCount int `json:"min-count"`
// Maximum resources expected
// Maximum number of resources expected. This maximum may be temporarily
// exceeded while resources are in the process of being deleted, though this
// is only expected when MaxCount is lowered.
MaxCount int `json:"max-count"`
// Lifespan of a resource, time after which the resource should be reset.
LifeSpan *time.Duration `json:"lifespan,omitempty"`
@@ -60,10 +62,7 @@ type DRLCByName []DynamicResourceLifeCycle
func (ut DRLCByName) Len() int { return len(ut) }
func (ut DRLCByName) Swap(i, j int) { ut[i], ut[j] = ut[j], ut[i] }
func (ut DRLCByName) Less(i, j int) bool { return ut[i].GetName() < ut[j].GetName() }
// GetName implements the Item interface used for storage
func (res DynamicResourceLifeCycle) GetName() string { return res.Type }
func (ut DRLCByName) Less(i, j int) bool { return ut[i].Type < ut[j].Type }
// NewDynamicResourceLifeCycleFromConfig parse the a ResourceEntry into a DynamicResourceLifeCycle
func NewDynamicResourceLifeCycleFromConfig(e ResourceEntry) DynamicResourceLifeCycle {
@@ -82,12 +81,6 @@ func NewDynamicResourceLifeCycleFromConfig(e ResourceEntry) DynamicResourceLifeC
}
}
// NewResourceFromNewDynamicResourceLifeCycle creates a resource from DynamicResourceLifeCycle given a name and a time.
// Using this method helps make sure all the resources are created the same way.
func NewResourceFromNewDynamicResourceLifeCycle(name string, dlrc *DynamicResourceLifeCycle, now time.Time) Resource {
return NewResource(name, dlrc.Type, dlrc.InitialState, "", now)
}
// Copy returns a copy of the TypeToResources
func (t TypeToResources) Copy() TypeToResources {
n := TypeToResources{}
@@ -97,15 +90,6 @@ func (t TypeToResources) Copy() TypeToResources {
return n
}
// ItemToDynamicResourceLifeCycle casts a Item back to a Resource
func ItemToDynamicResourceLifeCycle(i Item) (DynamicResourceLifeCycle, error) {
res, ok := i.(DynamicResourceLifeCycle)
if !ok {
return DynamicResourceLifeCycle{}, fmt.Errorf("cannot construct Resource from received object %v", i)
}
return res, nil
}
// GenerateDynamicResourceName generates a unique name for dynamic resources
func GenerateDynamicResourceName() string {
return uuid.New().String()

30
vendor/sigs.k8s.io/boskos/storage/BUILD.bazel generated vendored Normal file
View File

@@ -0,0 +1,30 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["storage.go"],
importpath = "k8s.io/test-infra/boskos/storage",
visibility = ["//visibility:public"],
deps = ["//boskos/common:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["storage_test.go"],
embed = [":go_default_library"],
deps = ["//boskos/common:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -21,79 +21,79 @@ import (
"fmt"
"k8s.io/test-infra/boskos/common"
"sigs.k8s.io/boskos/common"
)
// PersistenceLayer defines a simple interface to persists Boskos Information
type PersistenceLayer interface {
Add(i common.Item) error
Add(r common.Resource) error
Delete(name string) error
Update(i common.Item) (common.Item, error)
Get(name string) (common.Item, error)
List() ([]common.Item, error)
Update(r common.Resource) (common.Resource, error)
Get(name string) (common.Resource, error)
List() ([]common.Resource, error)
}
type inMemoryStore struct {
items map[string]common.Item
lock sync.RWMutex
resources map[string]common.Resource
lock sync.RWMutex
}
// NewMemoryStorage creates an in memory persistence layer
func NewMemoryStorage() PersistenceLayer {
return &inMemoryStore{
items: map[string]common.Item{},
resources: map[string]common.Resource{},
}
}
func (im *inMemoryStore) Add(i common.Item) error {
func (im *inMemoryStore) Add(r common.Resource) error {
im.lock.Lock()
defer im.lock.Unlock()
_, ok := im.items[i.GetName()]
_, ok := im.resources[r.Name]
if ok {
return fmt.Errorf("item %s already exists", i.GetName())
return fmt.Errorf("resource %s already exists", r.Name)
}
im.items[i.GetName()] = i
im.resources[r.Name] = r
return nil
}
func (im *inMemoryStore) Delete(name string) error {
im.lock.Lock()
defer im.lock.Unlock()
_, ok := im.items[name]
_, ok := im.resources[name]
if !ok {
return fmt.Errorf("cannot find item %s", name)
}
delete(im.items, name)
delete(im.resources, name)
return nil
}
func (im *inMemoryStore) Update(i common.Item) (common.Item, error) {
func (im *inMemoryStore) Update(r common.Resource) (common.Resource, error) {
im.lock.Lock()
defer im.lock.Unlock()
_, ok := im.items[i.GetName()]
_, ok := im.resources[r.Name]
if !ok {
return nil, fmt.Errorf("cannot find item %s", i.GetName())
return common.Resource{}, fmt.Errorf("cannot find item %s", r.Name)
}
im.items[i.GetName()] = i
return i, nil
im.resources[r.Name] = r
return r, nil
}
func (im *inMemoryStore) Get(name string) (common.Item, error) {
func (im *inMemoryStore) Get(name string) (common.Resource, error) {
im.lock.RLock()
defer im.lock.RUnlock()
i, ok := im.items[name]
r, ok := im.resources[name]
if !ok {
return nil, fmt.Errorf("cannot find item %s", name)
return common.Resource{}, fmt.Errorf("cannot find item %s", name)
}
return i, nil
return r, nil
}
func (im *inMemoryStore) List() ([]common.Item, error) {
func (im *inMemoryStore) List() ([]common.Resource, error) {
im.lock.RLock()
defer im.lock.RUnlock()
var items []common.Item
for _, i := range im.items {
items = append(items, i)
var resources []common.Resource
for _, r := range im.resources {
resources = append(resources, r)
}
return items, nil
return resources, nil
}

15
vendor/sigs.k8s.io/yaml/.travis.yml generated vendored
View File

@@ -1,14 +1,13 @@
language: go
dist: xenial
go:
- 1.9.x
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- diff -u <(echo -n) <(gofmt -d *.go)
- diff -u <(echo -n) <(golint $(go list -e ./...) | grep -v YAMLToJSON)
- go tool vet .
- go test -v -race ./...
- GO111MODULE=on go vet .
- GO111MODULE=on go test -v -race ./...
- git diff --exit-code
install:
- go get golang.org/x/lint/golint
- GO111MODULE=off go get golang.org/x/lint/golint

2
vendor/sigs.k8s.io/yaml/OWNERS generated vendored
View File

@@ -1,3 +1,5 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- dims
- lavalamp

14
vendor/sigs.k8s.io/yaml/README.md generated vendored
View File

@@ -1,12 +1,14 @@
# YAML marshaling and unmarshaling support for Go
[![Build Status](https://travis-ci.org/ghodss/yaml.svg)](https://travis-ci.org/ghodss/yaml)
[![Build Status](https://travis-ci.org/kubernetes-sigs/yaml.svg)](https://travis-ci.org/kubernetes-sigs/yaml)
kubernetes-sigs/yaml is a permanent fork of [ghodss/yaml](https://github.com/ghodss/yaml).
## Introduction
A wrapper around [go-yaml](https://github.com/go-yaml/yaml) designed to enable a better way of handling YAML when marshaling to and from structs.
In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/).
In short, this library first converts YAML to JSON using go-yaml and then uses `json.Marshal` and `json.Unmarshal` to convert to or from the struct. This means that it effectively reuses the JSON struct tags as well as the custom JSON methods `MarshalJSON` and `UnmarshalJSON` unlike go-yaml. For a detailed overview of the rationale behind this method, [see this blog post](http://web.archive.org/web/20190603050330/http://ghodss.com/2014/the-right-way-to-handle-yaml-in-golang/).
## Compatibility
@@ -32,13 +34,13 @@ GOOD:
To install, run:
```
$ go get github.com/ghodss/yaml
$ go get sigs.k8s.io/yaml
```
And import using:
```
import "github.com/ghodss/yaml"
import "sigs.k8s.io/yaml"
```
Usage is very similar to the JSON library:
@@ -49,7 +51,7 @@ package main
import (
"fmt"
"github.com/ghodss/yaml"
"sigs.k8s.io/yaml"
)
type Person struct {
@@ -93,7 +95,7 @@ package main
import (
"fmt"
"github.com/ghodss/yaml"
"sigs.k8s.io/yaml"
)
func main() {

61
vendor/sigs.k8s.io/yaml/yaml.go generated vendored
View File

@@ -317,3 +317,64 @@ func convertToJSONableObject(yamlObj interface{}, jsonTarget *reflect.Value) (in
return yamlObj, nil
}
}
// JSONObjectToYAMLObject converts an in-memory JSON object into a YAML in-memory MapSlice,
// without going through a byte representation. A nil or empty map[string]interface{} input is
// converted to an empty map, i.e. yaml.MapSlice(nil).
//
// interface{} slices stay interface{} slices. map[string]interface{} becomes yaml.MapSlice.
//
// int64 and float64 are down casted following the logic of github.com/go-yaml/yaml:
// - float64s are down-casted as far as possible without data-loss to int, int64, uint64.
// - int64s are down-casted to int if possible without data-loss.
//
// Big int/int64/uint64 do not lose precision as in the json-yaml roundtripping case.
//
// string, bool and any other types are unchanged.
func JSONObjectToYAMLObject(j map[string]interface{}) yaml.MapSlice {
if len(j) == 0 {
return nil
}
ret := make(yaml.MapSlice, 0, len(j))
for k, v := range j {
ret = append(ret, yaml.MapItem{Key: k, Value: jsonToYAMLValue(v)})
}
return ret
}
func jsonToYAMLValue(j interface{}) interface{} {
switch j := j.(type) {
case map[string]interface{}:
if j == nil {
return interface{}(nil)
}
return JSONObjectToYAMLObject(j)
case []interface{}:
if j == nil {
return interface{}(nil)
}
ret := make([]interface{}, len(j))
for i := range j {
ret[i] = jsonToYAMLValue(j[i])
}
return ret
case float64:
// replicate the logic in https://github.com/go-yaml/yaml/blob/51d6538a90f86fe93ac480b35f37b2be17fef232/resolve.go#L151
if i64 := int64(j); j == float64(i64) {
if i := int(i64); i64 == int64(i) {
return i
}
return i64
}
if ui64 := uint64(j); j == float64(ui64) {
return ui64
}
return j
case int64:
if i := int(j); j == int64(i) {
return i
}
return j
}
return j
}