Update godeps
This commit is contained in:
parent
8b25cc67a5
commit
a736fba0e1
769 changed files with 15495 additions and 7996 deletions
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/helpers.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2015 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.
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/latest.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/latest/latest.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/register.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/register.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/types.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/conversion.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/conversion.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/register.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/register.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/types.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api/v1/types.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/auth_loaders.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/auth_loaders.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/client_config.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
87
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/config.go
generated
vendored
87
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/config.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
@ -22,6 +22,7 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
|
|
@ -153,6 +154,17 @@ func NewDefaultPathOptions() *PathOptions {
|
|||
// that means that this code will only write into a single file. If you want to relativizePaths, you must provide a fully qualified path in any
|
||||
// modified element.
|
||||
func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, relativizePaths bool) error {
|
||||
possibleSources := configAccess.GetLoadingPrecedence()
|
||||
// sort the possible kubeconfig files so we always "lock" in the same order
|
||||
// to avoid deadlock (note: this can fail w/ symlinks, but... come on).
|
||||
sort.Strings(possibleSources)
|
||||
for _, filename := range possibleSources {
|
||||
if err := lockFile(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
defer unlockFile(filename)
|
||||
}
|
||||
|
||||
startingConfig, err := configAccess.GetStartingConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -186,7 +198,10 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, rela
|
|||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := GetConfigFromFileOrDie(destinationFile)
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := *cluster
|
||||
|
||||
configToWrite.Clusters[key] = &t
|
||||
|
|
@ -211,7 +226,10 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, rela
|
|||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := GetConfigFromFileOrDie(destinationFile)
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configToWrite.Contexts[key] = context
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
|
|
@ -228,7 +246,10 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, rela
|
|||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := GetConfigFromFileOrDie(destinationFile)
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t := *authInfo
|
||||
configToWrite.AuthInfos[key] = &t
|
||||
configToWrite.AuthInfos[key].LocationOfOrigin = destinationFile
|
||||
|
|
@ -251,7 +272,10 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, rela
|
|||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := GetConfigFromFileOrDie(destinationFile)
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(configToWrite.Clusters, key)
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
|
|
@ -267,7 +291,10 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, rela
|
|||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := GetConfigFromFileOrDie(destinationFile)
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(configToWrite.Contexts, key)
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
|
|
@ -283,7 +310,10 @@ func ModifyConfig(configAccess ConfigAccess, newConfig clientcmdapi.Config, rela
|
|||
destinationFile = configAccess.GetDefaultFilename()
|
||||
}
|
||||
|
||||
configToWrite := GetConfigFromFileOrDie(destinationFile)
|
||||
configToWrite, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
delete(configToWrite.AuthInfos, key)
|
||||
|
||||
if err := WriteToFile(*configToWrite, destinationFile); err != nil {
|
||||
|
|
@ -330,7 +360,10 @@ func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) er
|
|||
|
||||
if configAccess.IsExplicitFile() {
|
||||
file := configAccess.GetExplicitFile()
|
||||
currConfig := GetConfigFromFileOrDie(file)
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
|
|
@ -341,7 +374,10 @@ func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) er
|
|||
|
||||
if len(newCurrentContext) > 0 {
|
||||
destinationFile := configAccess.GetDefaultFilename()
|
||||
config := GetConfigFromFileOrDie(destinationFile)
|
||||
config, err := getConfigFromFile(destinationFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.CurrentContext = newCurrentContext
|
||||
|
||||
if err := WriteToFile(*config, destinationFile); err != nil {
|
||||
|
|
@ -354,7 +390,10 @@ func writeCurrentContext(configAccess ConfigAccess, newCurrentContext string) er
|
|||
// we're supposed to be clearing the current context. We need to find the first spot in the chain that is setting it and clear it
|
||||
for _, file := range configAccess.GetLoadingPrecedence() {
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
currConfig := GetConfigFromFileOrDie(file)
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(currConfig.CurrentContext) > 0 {
|
||||
currConfig.CurrentContext = newCurrentContext
|
||||
|
|
@ -379,7 +418,10 @@ func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferenc
|
|||
|
||||
if configAccess.IsExplicitFile() {
|
||||
file := configAccess.GetExplicitFile()
|
||||
currConfig := GetConfigFromFileOrDie(file)
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currConfig.Preferences = newPrefs
|
||||
if err := WriteToFile(*currConfig, file); err != nil {
|
||||
return err
|
||||
|
|
@ -389,7 +431,10 @@ func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferenc
|
|||
}
|
||||
|
||||
for _, file := range configAccess.GetLoadingPrecedence() {
|
||||
currConfig := GetConfigFromFileOrDie(file)
|
||||
currConfig, err := getConfigFromFile(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(currConfig.Preferences, newPrefs) {
|
||||
currConfig.Preferences = newPrefs
|
||||
|
|
@ -404,15 +449,23 @@ func writePreferences(configAccess ConfigAccess, newPrefs clientcmdapi.Preferenc
|
|||
return errors.New("no config found to write preferences")
|
||||
}
|
||||
|
||||
// GetConfigFromFileOrDie tries to read a kubeconfig file and if it can't, it calls exit. One exception, missing files result in empty configs, not an exit
|
||||
func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config {
|
||||
// getConfigFromFile tries to read a kubeconfig file and if it can't, returns an error. One exception, missing files result in empty configs, not an error.
|
||||
func getConfigFromFile(filename string) (*clientcmdapi.Config, error) {
|
||||
config, err := LoadFromFile(filename)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
glog.FatalDepth(1, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
return clientcmdapi.NewConfig()
|
||||
config = clientcmdapi.NewConfig()
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// GetConfigFromFileOrDie tries to read a kubeconfig file and if it can't, it calls exit. One exception, missing files result in empty configs, not an exit
|
||||
func GetConfigFromFileOrDie(filename string) *clientcmdapi.Config {
|
||||
config, err := getConfigFromFile(filename)
|
||||
if err != nil {
|
||||
glog.FatalDepth(1, err)
|
||||
}
|
||||
|
||||
return config
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/doc.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/doc.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
37
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader.go
generated
vendored
37
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/loader.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
@ -230,14 +230,17 @@ func (rules *ClientConfigLoadingRules) Migrate() error {
|
|||
if _, err := os.Stat(destination); err == nil {
|
||||
// if the destination already exists, do nothing
|
||||
continue
|
||||
} else if os.IsPermission(err) {
|
||||
// if we can't access the file, skip it
|
||||
continue
|
||||
} else if !os.IsNotExist(err) {
|
||||
// if we had an error other than non-existence, fail
|
||||
return err
|
||||
}
|
||||
|
||||
if sourceInfo, err := os.Stat(source); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// if the source file doesn't exist, there's no work to do.
|
||||
if os.IsNotExist(err) || os.IsPermission(err) {
|
||||
// if the source file doesn't exist or we can't access it, there's no work to do.
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -381,12 +384,40 @@ func WriteToFile(config clientcmdapi.Config, filename string) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filename, content, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func lockFile(filename string) error {
|
||||
// TODO: find a way to do this with actual file locks. Will
|
||||
// probably need seperate solution for windows and linux.
|
||||
|
||||
// Make sure the dir exists before we try to create a lock file.
|
||||
dir := filepath.Dir(filename)
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
f, err := os.OpenFile(lockName(filename), os.O_CREATE|os.O_EXCL, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func unlockFile(filename string) error {
|
||||
return os.Remove(lockName(filename))
|
||||
}
|
||||
|
||||
func lockName(filename string) string {
|
||||
return filename + ".lock"
|
||||
}
|
||||
|
||||
// Write serializes the config to yaml.
|
||||
// Encapsulates serialization without assuming the destination is a file.
|
||||
func Write(config clientcmdapi.Config) ([]byte, error) {
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/merged_client_builder.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
20
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/overrides.go
generated
vendored
20
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/overrides.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
@ -124,12 +124,12 @@ const (
|
|||
// RecommendedAuthOverrideFlags is a convenience method to return recommended flag names prefixed with a string of your choosing
|
||||
func RecommendedAuthOverrideFlags(prefix string) AuthOverrideFlags {
|
||||
return AuthOverrideFlags{
|
||||
ClientCertificate: FlagInfo{prefix + FlagCertFile, "", "", "Path to a client certificate file for TLS."},
|
||||
ClientKey: FlagInfo{prefix + FlagKeyFile, "", "", "Path to a client key file for TLS."},
|
||||
Token: FlagInfo{prefix + FlagBearerToken, "", "", "Bearer token for authentication to the API server."},
|
||||
Impersonate: FlagInfo{prefix + FlagImpersonate, "", "", "Username to impersonate for the operation."},
|
||||
Username: FlagInfo{prefix + FlagUsername, "", "", "Username for basic authentication to the API server."},
|
||||
Password: FlagInfo{prefix + FlagPassword, "", "", "Password for basic authentication to the API server."},
|
||||
ClientCertificate: FlagInfo{prefix + FlagCertFile, "", "", "Path to a client certificate file for TLS"},
|
||||
ClientKey: FlagInfo{prefix + FlagKeyFile, "", "", "Path to a client key file for TLS"},
|
||||
Token: FlagInfo{prefix + FlagBearerToken, "", "", "Bearer token for authentication to the API server"},
|
||||
Impersonate: FlagInfo{prefix + FlagImpersonate, "", "", "Username to impersonate for the operation"},
|
||||
Username: FlagInfo{prefix + FlagUsername, "", "", "Username for basic authentication to the API server"},
|
||||
Password: FlagInfo{prefix + FlagPassword, "", "", "Password for basic authentication to the API server"},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -138,8 +138,8 @@ func RecommendedClusterOverrideFlags(prefix string) ClusterOverrideFlags {
|
|||
return ClusterOverrideFlags{
|
||||
APIServer: FlagInfo{prefix + FlagAPIServer, "", "", "The address and port of the Kubernetes API server"},
|
||||
APIVersion: FlagInfo{prefix + FlagAPIVersion, "", "", "DEPRECATED: The API version to use when talking to the server"},
|
||||
CertificateAuthority: FlagInfo{prefix + FlagCAFile, "", "", "Path to a cert. file for the certificate authority."},
|
||||
InsecureSkipTLSVerify: FlagInfo{prefix + FlagInsecure, "", "false", "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure."},
|
||||
CertificateAuthority: FlagInfo{prefix + FlagCAFile, "", "", "Path to a cert. file for the certificate authority"},
|
||||
InsecureSkipTLSVerify: FlagInfo{prefix + FlagInsecure, "", "false", "If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure"},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ func RecommendedContextOverrideFlags(prefix string) ContextOverrideFlags {
|
|||
return ContextOverrideFlags{
|
||||
ClusterName: FlagInfo{prefix + FlagClusterName, "", "", "The name of the kubeconfig cluster to use"},
|
||||
AuthInfoName: FlagInfo{prefix + FlagAuthInfoName, "", "", "The name of the kubeconfig user to use"},
|
||||
Namespace: FlagInfo{prefix + FlagNamespace, "", "", "If present, the namespace scope for this CLI request."},
|
||||
Namespace: FlagInfo{prefix + FlagNamespace, "", "", "If present, the namespace scope for this CLI request"},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/validation.go
generated
vendored
2
vendor/k8s.io/kubernetes/pkg/client/unversioned/clientcmd/validation.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
Copyright 2014 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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue