Initial aggregated API server (#15)
Add initial aggregated API server (squashed from a bunch of commits). Signed-off-by: Andrew Keesler <akeesler@vmware.com> Signed-off-by: Aram Price <pricear@vmware.com> Signed-off-by: Ryan Richard <richardry@vmware.com>
This commit is contained in:
parent
23c1b32a02
commit
5fdc20886d
10
Dockerfile
10
Dockerfile
@ -25,8 +25,12 @@ WORKDIR /work
|
|||||||
COPY go.mod .
|
COPY go.mod .
|
||||||
COPY go.sum .
|
COPY go.sum .
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
# Copy the source code
|
# Copy only the production source code to avoid cache misses when editing other files
|
||||||
COPY . .
|
COPY cmd ./cmd
|
||||||
|
COPY internal ./internal
|
||||||
|
COPY pkg ./pkg
|
||||||
|
COPY tools ./tools
|
||||||
|
COPY hack ./hack
|
||||||
# Build the executable binary
|
# Build the executable binary
|
||||||
RUN GOOS=linux GOARCH=amd64 go build -ldflags "$(hack/get-ldflags.sh)" -o out ./...
|
RUN GOOS=linux GOARCH=amd64 go build -ldflags "$(hack/get-ldflags.sh)" -o out ./...
|
||||||
|
|
||||||
@ -37,6 +41,6 @@ WORKDIR /root/
|
|||||||
# Copy the binary from the build-env stage
|
# Copy the binary from the build-env stage
|
||||||
COPY --from=build-env /work/out/placeholder-name placeholder-name
|
COPY --from=build-env /work/out/placeholder-name placeholder-name
|
||||||
# Document the port
|
# Document the port
|
||||||
EXPOSE 8080
|
EXPOSE 443
|
||||||
# Set the command
|
# Set the command
|
||||||
CMD ["./placeholder-name"]
|
CMD ["./placeholder-name"]
|
||||||
|
@ -9,38 +9,40 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"errors"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"k8s.io/apiserver/pkg/server/dynamiccertificates"
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
|
||||||
restclient "k8s.io/client-go/rest"
|
|
||||||
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||||
aggregationv1client "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
|
||||||
|
|
||||||
placeholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1"
|
|
||||||
"github.com/suzerain-io/placeholder-name/internal/autoregistration"
|
"github.com/suzerain-io/placeholder-name/internal/autoregistration"
|
||||||
"github.com/suzerain-io/placeholder-name/internal/certauthority"
|
"github.com/suzerain-io/placeholder-name/internal/certauthority"
|
||||||
"github.com/suzerain-io/placeholder-name/internal/downward"
|
"github.com/suzerain-io/placeholder-name/internal/downward"
|
||||||
"github.com/suzerain-io/placeholder-name/pkg/config"
|
|
||||||
"github.com/suzerain-io/placeholder-name/pkg/handlers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// shutdownGracePeriod controls how long active connections are allowed to continue at shutdown.
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
const shutdownGracePeriod = 5 * time.Second
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
|
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||||
|
"k8s.io/apiserver/plugin/pkg/authenticator/token/webhook"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
aggregationv1client "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
|
||||||
|
|
||||||
|
placeholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1"
|
||||||
|
"github.com/suzerain-io/placeholder-name/pkg/apiserver"
|
||||||
|
"github.com/suzerain-io/placeholder-name/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
// App is an object that represents the placeholder-name application.
|
// App is an object that represents the placeholder-name application.
|
||||||
type App struct {
|
type App struct {
|
||||||
@ -50,22 +52,23 @@ type App struct {
|
|||||||
configPath string
|
configPath string
|
||||||
downwardAPIPath string
|
downwardAPIPath string
|
||||||
|
|
||||||
// listen address for healthz serve
|
recommendedOptions *genericoptions.RecommendedOptions
|
||||||
healthAddr string
|
|
||||||
|
|
||||||
// listen address for main serve
|
|
||||||
mainAddr string
|
|
||||||
|
|
||||||
// webhook authenticates tokens
|
|
||||||
webhook authenticator.Token
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is ignored for now because we turn off etcd storage below, but this is
|
||||||
|
// the right prefix in case we turn it back on.
|
||||||
|
const defaultEtcdPathPrefix = "/registry/" + placeholderv1alpha1.GroupName
|
||||||
|
|
||||||
// New constructs a new App with command line args, stdout and stderr.
|
// New constructs a new App with command line args, stdout and stderr.
|
||||||
func New(args []string, stdout, stderr io.Writer) *App {
|
func New(ctx context.Context, args []string, stdout, stderr io.Writer) *App {
|
||||||
a := &App{
|
a := &App{
|
||||||
healthAddr: ":8080",
|
recommendedOptions: genericoptions.NewRecommendedOptions(
|
||||||
mainAddr: ":443",
|
defaultEtcdPathPrefix,
|
||||||
|
apiserver.Codecs.LegacyCodec(placeholderv1alpha1.SchemeGroupVersion),
|
||||||
|
// TODO we should check to see if all the other default settings are acceptable for us
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
a.recommendedOptions.Etcd = nil // turn off etcd storage because we don't need it yet
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: `placeholder-name`,
|
Use: `placeholder-name`,
|
||||||
@ -93,7 +96,8 @@ authenticating to the Kubernetes API.`,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
return fmt.Errorf("could not initialize Kubernetes client: %w", err)
|
||||||
}
|
}
|
||||||
return a.serve(context.Background(), k8s.CoreV1(), aggregation)
|
|
||||||
|
return a.run(ctx, k8s.CoreV1(), aggregation)
|
||||||
},
|
},
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
}
|
}
|
||||||
@ -126,23 +130,28 @@ func (a *App) Run() error {
|
|||||||
return a.cmd.Execute()
|
return a.cmd.Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) serve(ctx context.Context, k8s corev1client.CoreV1Interface, aggregation aggregationv1client.Interface) error {
|
func (a *App) run(
|
||||||
|
ctx context.Context,
|
||||||
|
k8s corev1client.CoreV1Interface,
|
||||||
|
aggregation aggregationv1client.Interface,
|
||||||
|
) error {
|
||||||
cfg, err := config.FromPath(a.configPath)
|
cfg, err := config.FromPath(a.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not load config: %w", err)
|
return fmt.Errorf("could not load config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
webhook, err := config.NewWebhook(cfg.WebhookConfig)
|
webhookTokenAuthenticator, err := config.NewWebhook(cfg.WebhookConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could create webhook client: %w", err)
|
return fmt.Errorf("could create webhook client: %w", err)
|
||||||
}
|
}
|
||||||
a.webhook = webhook
|
|
||||||
|
|
||||||
podinfo, err := downward.Load(a.downwardAPIPath)
|
podinfo, err := downward.Load(a.downwardAPIPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not read pod metadata: %w", err)
|
return fmt.Errorf("could not read pod metadata: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO use the postStart hook to generate certs?
|
||||||
|
|
||||||
ca, err := certauthority.New(pkix.Name{CommonName: "Placeholder CA"})
|
ca, err := certauthority.New(pkix.Name{CommonName: "Placeholder CA"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not initialize CA: %w", err)
|
return fmt.Errorf("could not initialize CA: %w", err)
|
||||||
@ -153,27 +162,26 @@ func (a *App) serve(ctx context.Context, k8s corev1client.CoreV1Interface, aggre
|
|||||||
}
|
}
|
||||||
log.Printf("initialized CA bundle:\n%s", string(caBundle))
|
log.Printf("initialized CA bundle:\n%s", string(caBundle))
|
||||||
|
|
||||||
|
const serviceName = "placeholder-name-api"
|
||||||
|
|
||||||
cert, err := ca.Issue(
|
cert, err := ca.Issue(
|
||||||
pkix.Name{CommonName: "Placeholder Server"},
|
pkix.Name{CommonName: serviceName + "." + podinfo.Namespace + ".svc"},
|
||||||
[]string{"placeholder-serve"},
|
[]string{},
|
||||||
24*365*time.Hour,
|
24*365*time.Hour,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not issue serving certificate: %w", err)
|
return fmt.Errorf("could not issue serving certificate: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start an errgroup to manage the lifetimes of the various listener goroutines.
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
|
||||||
|
|
||||||
// Dynamically register our v1alpha1 API service.
|
// Dynamically register our v1alpha1 API service.
|
||||||
service := corev1.Service{
|
service := corev1.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "placeholder-name-api"},
|
ObjectMeta: metav1.ObjectMeta{Name: serviceName},
|
||||||
Spec: corev1.ServiceSpec{
|
Spec: corev1.ServiceSpec{
|
||||||
Ports: []corev1.ServicePort{
|
Ports: []corev1.ServicePort{
|
||||||
{
|
{
|
||||||
Protocol: corev1.ProtocolTCP,
|
Protocol: corev1.ProtocolTCP,
|
||||||
Port: 443,
|
Port: 443,
|
||||||
TargetPort: intstr.IntOrString{IntVal: 443}, //TODO: parse this out of mainAddr
|
TargetPort: intstr.IntOrString{IntVal: 443},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Selector: podinfo.Labels,
|
Selector: podinfo.Labels,
|
||||||
@ -188,8 +196,8 @@ func (a *App) serve(ctx context.Context, k8s corev1client.CoreV1Interface, aggre
|
|||||||
Group: placeholderv1alpha1.GroupName,
|
Group: placeholderv1alpha1.GroupName,
|
||||||
Version: placeholderv1alpha1.SchemeGroupVersion.Version,
|
Version: placeholderv1alpha1.SchemeGroupVersion.Version,
|
||||||
CABundle: caBundle,
|
CABundle: caBundle,
|
||||||
GroupPriorityMinimum: 2500,
|
GroupPriorityMinimum: 2500, // TODO what is the right value? https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#apiservicespec-v1beta1-apiregistration-k8s-io
|
||||||
VersionPriority: 10,
|
VersionPriority: 10, // TODO what is the right value? https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#apiservicespec-v1beta1-apiregistration-k8s-io
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := autoregistration.Setup(ctx, autoregistration.SetupOptions{
|
if err := autoregistration.Setup(ctx, autoregistration.SetupOptions{
|
||||||
@ -202,66 +210,38 @@ func (a *App) serve(ctx context.Context, k8s corev1client.CoreV1Interface, aggre
|
|||||||
return fmt.Errorf("could not register API service: %w", err)
|
return fmt.Errorf("could not register API service: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start healthz listener
|
apiServerConfig, err := a.ConfigServer(cert, webhookTokenAuthenticator)
|
||||||
eg.Go(func() error {
|
if err != nil {
|
||||||
log.Printf("Starting healthz serve on %v", a.healthAddr)
|
|
||||||
server := http.Server{
|
|
||||||
BaseContext: func(_ net.Listener) context.Context { return ctx },
|
|
||||||
Addr: a.healthAddr,
|
|
||||||
Handler: handlers.New(),
|
|
||||||
}
|
|
||||||
return runGracefully(ctx, &server, eg, server.ListenAndServe)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Start main service listener
|
|
||||||
eg.Go(func() error {
|
|
||||||
log.Printf("Starting main serve on %v", a.mainAddr)
|
|
||||||
server := http.Server{
|
|
||||||
BaseContext: func(_ net.Listener) context.Context { return ctx },
|
|
||||||
Addr: a.mainAddr,
|
|
||||||
TLSConfig: &tls.Config{
|
|
||||||
MinVersion: tls.VersionTLS12,
|
|
||||||
Certificates: []tls.Certificate{*cert},
|
|
||||||
},
|
|
||||||
Handler: http.HandlerFunc(a.exampleHandler),
|
|
||||||
}
|
|
||||||
return runGracefully(ctx, &server, eg, func() error {
|
|
||||||
// Doc for ListenAndServeTLS says we can pass empty strings if we configured
|
|
||||||
// keypair for TLS in http.Server.TLSConfig.
|
|
||||||
return server.ListenAndServeTLS("", "")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := eg.Wait(); !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
server, err := apiServerConfig.Complete().New()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not issue serving certificate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
||||||
}
|
}
|
||||||
|
|
||||||
// exampleHandler is a stub to be replaced with our real server logic.
|
func (a *App) ConfigServer(cert *tls.Certificate, webhookTokenAuthenticator *webhook.WebhookTokenAuthenticator) (*apiserver.Config, error) {
|
||||||
func (a *App) exampleHandler(w http.ResponseWriter, r *http.Request) {
|
provider, err := createStaticCertKeyProvider(cert)
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)
|
if err != nil {
|
||||||
defer cancel()
|
return nil, fmt.Errorf("could not create static cert key provider: %w", err)
|
||||||
|
}
|
||||||
|
a.recommendedOptions.SecureServing.ServerCert.GeneratedCert = provider
|
||||||
|
|
||||||
rsp, authenticated, err := a.webhook.AuthenticateToken(ctx, "")
|
serverConfig := genericapiserver.NewRecommendedConfig(apiserver.Codecs)
|
||||||
log.Printf("token response: %+v", rsp)
|
if err := a.recommendedOptions.ApplyTo(serverConfig); err != nil {
|
||||||
log.Printf("token authenticated: %+v", authenticated)
|
return nil, err
|
||||||
log.Printf("token err: %+v", err)
|
}
|
||||||
|
|
||||||
_, _ = w.Write([]byte("hello world"))
|
apiServerConfig := &apiserver.Config{
|
||||||
}
|
GenericConfig: serverConfig,
|
||||||
|
ExtraConfig: apiserver.ExtraConfig{
|
||||||
// runGracefully runs an http.Server with graceful shutdown.
|
Webhook: webhookTokenAuthenticator,
|
||||||
func runGracefully(ctx context.Context, srv *http.Server, eg *errgroup.Group, f func() error) error {
|
},
|
||||||
// Start the listener in a child goroutine.
|
}
|
||||||
eg.Go(f)
|
return apiServerConfig, nil
|
||||||
|
|
||||||
// If/when the context is canceled or times out, initiate shutting down the serve.
|
|
||||||
<-ctx.Done()
|
|
||||||
|
|
||||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), shutdownGracePeriod)
|
|
||||||
defer cancel()
|
|
||||||
return srv.Shutdown(shutdownCtx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createProtoKubeConfig returns a copy of the input config with the ContentConfig set to use protobuf.
|
// createProtoKubeConfig returns a copy of the input config with the ContentConfig set to use protobuf.
|
||||||
@ -273,3 +253,27 @@ func createProtoKubeConfig(kubeConfig *restclient.Config) *restclient.Config {
|
|||||||
protoKubeConfig.ContentType = runtime.ContentTypeProtobuf
|
protoKubeConfig.ContentType = runtime.ContentTypeProtobuf
|
||||||
return protoKubeConfig
|
return protoKubeConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createStaticCertKeyProvider(cert *tls.Certificate) (dynamiccertificates.CertKeyContentProvider, error) {
|
||||||
|
privateKeyDER, err := x509.MarshalPKCS8PrivateKey(cert.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshalling private key: %w", err)
|
||||||
|
}
|
||||||
|
privateKeyPEM := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "PRIVATE KEY",
|
||||||
|
Headers: nil,
|
||||||
|
Bytes: privateKeyDER,
|
||||||
|
})
|
||||||
|
|
||||||
|
certChainPEM := make([]byte, 0)
|
||||||
|
for _, certFromChain := range cert.Certificate {
|
||||||
|
certPEMBytes := pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: "CERTIFICATE",
|
||||||
|
Headers: nil,
|
||||||
|
Bytes: certFromChain,
|
||||||
|
})
|
||||||
|
certChainPEM = append(certChainPEM, certPEMBytes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dynamiccertificates.NewStaticCertKeyContent("some-name???", certChainPEM, privateKeyPEM)
|
||||||
|
}
|
||||||
|
@ -10,14 +10,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
corev1fake "k8s.io/client-go/kubernetes/fake"
|
|
||||||
aggregationv1fake "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const knownGoodUsage = `
|
const knownGoodUsage = `
|
||||||
@ -32,6 +28,7 @@ Flags:
|
|||||||
-c, --config string path to configuration file (default "placeholder-name.yaml")
|
-c, --config string path to configuration file (default "placeholder-name.yaml")
|
||||||
--downward-api-path string path to Downward API volume mount (default "/etc/podinfo")
|
--downward-api-path string path to Downward API volume mount (default "/etc/podinfo")
|
||||||
-h, --help help for placeholder-name
|
-h, --help help for placeholder-name
|
||||||
|
--log-flush-frequency duration Maximum number of seconds between log flushes (default 5s)
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestCommand(t *testing.T) {
|
func TestCommand(t *testing.T) {
|
||||||
@ -78,7 +75,7 @@ func TestCommand(t *testing.T) {
|
|||||||
stdout := bytes.NewBuffer([]byte{})
|
stdout := bytes.NewBuffer([]byte{})
|
||||||
stderr := bytes.NewBuffer([]byte{})
|
stderr := bytes.NewBuffer([]byte{})
|
||||||
|
|
||||||
a := New(test.args, stdout, stderr)
|
a := New(context.Background(), test.args, stdout, stderr)
|
||||||
a.cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
a.cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -89,45 +86,8 @@ func TestCommand(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
if test.wantStdout != "" {
|
if test.wantStdout != "" {
|
||||||
require.Equal(t, strings.TrimSpace(test.wantStdout), strings.TrimSpace(stdout.String()))
|
require.Equal(t, strings.TrimSpace(test.wantStdout), strings.TrimSpace(stdout.String()), cmp.Diff(test.wantStdout, stdout.String()))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServeApp(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
fakev1 := corev1fake.NewSimpleClientset(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}})
|
|
||||||
fakeaggregationv1 := aggregationv1fake.NewSimpleClientset()
|
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
||||||
cancel()
|
|
||||||
|
|
||||||
a := App{
|
|
||||||
healthAddr: "127.0.0.1:0",
|
|
||||||
mainAddr: "127.0.0.1:8443",
|
|
||||||
configPath: "testdata/valid-config.yaml",
|
|
||||||
downwardAPIPath: "testdata/podinfo",
|
|
||||||
}
|
|
||||||
err := a.serve(ctx, fakev1.CoreV1(), fakeaggregationv1)
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("failure", func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
a := App{
|
|
||||||
healthAddr: "127.0.0.1:8081",
|
|
||||||
mainAddr: "127.0.0.1:8081",
|
|
||||||
configPath: "testdata/valid-config.yaml",
|
|
||||||
downwardAPIPath: "testdata/podinfo",
|
|
||||||
}
|
|
||||||
err := a.serve(ctx, fakev1.CoreV1(), fakeaggregationv1)
|
|
||||||
require.EqualError(t, err, "listen tcp 127.0.0.1:8081: bind: address already in use")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
@ -8,16 +8,24 @@ package main
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/client-go/pkg/version"
|
"k8s.io/client-go/pkg/version"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
|
"k8s.io/component-base/logs"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
"github.com/suzerain-io/placeholder-name/cmd/placeholder-name/app"
|
"github.com/suzerain-io/placeholder-name/cmd/placeholder-name/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
logs.InitLogs()
|
||||||
|
defer logs.FlushLogs()
|
||||||
|
|
||||||
klog.Infof("Running %s at %#v", rest.DefaultKubernetesUserAgent(), version.Get())
|
klog.Infof("Running %s at %#v", rest.DefaultKubernetesUserAgent(), version.Get())
|
||||||
if err := app.New(os.Args[1:], os.Stdout, os.Stderr).Run(); err != nil {
|
|
||||||
os.Exit(1)
|
ctx := genericapiserver.SetupSignalContext()
|
||||||
|
|
||||||
|
if err := app.New(ctx, os.Args[1:], os.Stdout, os.Stderr).Run(); err != nil {
|
||||||
|
klog.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,11 +23,14 @@ metadata:
|
|||||||
app: #@ data.values.app_name
|
app: #@ data.values.app_name
|
||||||
data:
|
data:
|
||||||
#@yaml/text-templated-strings
|
#@yaml/text-templated-strings
|
||||||
placeholder-config.yaml: |
|
placeholder-name.yaml: |
|
||||||
webhook:
|
webhook:
|
||||||
url: (@= data.values.webhook_url @)
|
url: (@= data.values.webhook_url @)
|
||||||
caBundle: (@= data.values.webhook_ca_bundle @)
|
caBundle: (@= data.values.webhook_ca_bundle @)
|
||||||
---
|
---
|
||||||
|
#! TODO set up healthy, ready, etc. probes correctly for our deployment
|
||||||
|
#! TODO set the priority-critical-urgent on our deployment to ask kube to never let it die
|
||||||
|
#! TODO set resource minimums (e.g. 512MB RAM) on the deployment to make sure we get scheduled onto a reasonable node
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
@ -36,7 +39,7 @@ metadata:
|
|||||||
labels:
|
labels:
|
||||||
app: #@ data.values.app_name
|
app: #@ data.values.app_name
|
||||||
spec:
|
spec:
|
||||||
replicas: 1
|
replicas: 1 #! TODO more than one replica for high availability, and share the same serving certificate among them (maybe using client-go leader election)
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: #@ data.values.app_name
|
app: #@ data.values.app_name
|
||||||
@ -57,7 +60,7 @@ spec:
|
|||||||
command:
|
command:
|
||||||
- ./placeholder-name
|
- ./placeholder-name
|
||||||
args:
|
args:
|
||||||
- --config=/etc/config/placeholder-config.yaml
|
- --config=/etc/config/placeholder-name.yaml
|
||||||
- --downward-api-path=/etc/podinfo
|
- --downward-api-path=/etc/podinfo
|
||||||
volumeMounts:
|
volumeMounts:
|
||||||
- name: config-volume
|
- name: config-volume
|
||||||
|
@ -12,6 +12,9 @@ rules:
|
|||||||
- apiGroups: [apiregistration.k8s.io]
|
- apiGroups: [apiregistration.k8s.io]
|
||||||
resources: [apiservices]
|
resources: [apiservices]
|
||||||
verbs: [create, get, list, patch, update, watch]
|
verbs: [create, get, list, patch, update, watch]
|
||||||
|
- apiGroups: [admissionregistration.k8s.io]
|
||||||
|
resources: [validatingwebhookconfigurations, mutatingwebhookconfigurations]
|
||||||
|
verbs: [get, list, watch]
|
||||||
---
|
---
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
@ -49,3 +52,59 @@ roleRef:
|
|||||||
kind: Role
|
kind: Role
|
||||||
name: #@ data.values.app_name + "-aggregated-api-server-role"
|
name: #@ data.values.app_name + "-aggregated-api-server-role"
|
||||||
apiGroup: rbac.authorization.k8s.io
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: #@ data.values.app_name + "-loginrequests-cluster-role"
|
||||||
|
rules:
|
||||||
|
- apiGroups: [placeholder.suzerain-io.github.io]
|
||||||
|
resources: [loginrequests]
|
||||||
|
verbs: [create]
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: #@ data.values.app_name + "-loginrequests-cluster-role-binding"
|
||||||
|
subjects:
|
||||||
|
#! both authenticated and unauthenticated requests (i.e. all requests) should be allowed
|
||||||
|
- kind: Group
|
||||||
|
name: system:authenticated
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
- kind: Group
|
||||||
|
name: system:unauthenticated
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
roleRef:
|
||||||
|
kind: ClusterRole
|
||||||
|
name: #@ data.values.app_name + "-loginrequests-cluster-role"
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
---
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: #@ data.values.app_name + "-service-account-cluster-role-binding"
|
||||||
|
namespace: #@ data.values.namespace
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: #@ data.values.app_name + "-service-account"
|
||||||
|
namespace: #@ data.values.namespace
|
||||||
|
roleRef:
|
||||||
|
kind: ClusterRole
|
||||||
|
#! give permissions for subjectaccessreviews, tokenreview that is needed by aggregated api servers
|
||||||
|
name: system:auth-delegator
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
---
|
||||||
|
kind: RoleBinding
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
metadata:
|
||||||
|
name: #@ data.values.app_name + "-extension-apiserver-authentication-reader-role-binding"
|
||||||
|
namespace: kube-system
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: #@ data.values.app_name + "-service-account"
|
||||||
|
namespace: #@ data.values.namespace
|
||||||
|
roleRef:
|
||||||
|
kind: Role
|
||||||
|
#! give permissions for a special configmap of CA bundles that is needed by aggregated api servers
|
||||||
|
name: extension-apiserver-authentication-reader
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
4
go.mod
4
go.mod
@ -5,15 +5,17 @@ go 1.14
|
|||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/golangci/golangci-lint v1.28.1
|
github.com/golangci/golangci-lint v1.28.1
|
||||||
|
github.com/google/go-cmp v0.4.0
|
||||||
github.com/spf13/cobra v1.0.0
|
github.com/spf13/cobra v1.0.0
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/suzerain-io/placeholder-name-api v0.0.0-20200714184318-8ad91581433a
|
github.com/suzerain-io/placeholder-name-api v0.0.0-20200714184318-8ad91581433a
|
||||||
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
|
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200714203950-a414963b4f95
|
||||||
golang.org/x/tools v0.0.0-20200707134715-9e0a013e855f // indirect
|
golang.org/x/tools v0.0.0-20200707134715-9e0a013e855f // indirect
|
||||||
k8s.io/api v0.19.0-rc.0
|
k8s.io/api v0.19.0-rc.0
|
||||||
k8s.io/apimachinery v0.19.0-rc.0
|
k8s.io/apimachinery v0.19.0-rc.0
|
||||||
k8s.io/apiserver v0.19.0-rc.0
|
k8s.io/apiserver v0.19.0-rc.0
|
||||||
k8s.io/client-go v0.19.0-rc.0
|
k8s.io/client-go v0.19.0-rc.0
|
||||||
|
k8s.io/component-base v0.19.0-rc.0
|
||||||
k8s.io/klog/v2 v2.2.0
|
k8s.io/klog/v2 v2.2.0
|
||||||
k8s.io/kube-aggregator v0.19.0-rc.0
|
k8s.io/kube-aggregator v0.19.0-rc.0
|
||||||
k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19
|
k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19
|
||||||
|
36
go.sum
36
go.sum
@ -31,13 +31,16 @@ github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5 h1:XTrzB+F8+SpRm
|
|||||||
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
github.com/Djarvur/go-err113 v0.0.0-20200511133814-5174e21577d5/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs=
|
||||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46 h1:lsxEuwrXEAokXB9qhlbKWPpo3KMLZQ5WB5WLQRW1uq0=
|
||||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
|
github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us=
|
||||||
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
|
github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM=
|
||||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
|
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
@ -67,16 +70,20 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
|||||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
|
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y=
|
||||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
|
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
|
||||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
@ -86,14 +93,17 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/denis-tingajkin/go-header v0.3.1 h1:ymEpSiFjeItCy1FOP+x0M2KdCELdEAHUsNa8F+hHc6w=
|
github.com/denis-tingajkin/go-header v0.3.1 h1:ymEpSiFjeItCy1FOP+x0M2KdCELdEAHUsNa8F+hHc6w=
|
||||||
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
|
github.com/denis-tingajkin/go-header v0.3.1/go.mod h1:sq/2IxMhaZX+RRcgHfCRx/m0M5na0fBt4/CRe7Lrji0=
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
|
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
|
||||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
@ -122,14 +132,18 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg
|
|||||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||||
|
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||||
|
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
|
||||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||||
|
github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
|
||||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
@ -218,6 +232,7 @@ github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunE
|
|||||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
|
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys=
|
||||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
@ -244,15 +259,19 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
|
|||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||||
github.com/gostaticanalysis/analysisutil v0.0.3 h1:iwp+5/UAyzQSFgQ4uR2sni99sJ8Eo9DEacKWM5pekIg=
|
github.com/gostaticanalysis/analysisutil v0.0.3 h1:iwp+5/UAyzQSFgQ4uR2sni99sJ8Eo9DEacKWM5pekIg=
|
||||||
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4 h1:z53tR0945TRRQO/fLEVPI6SMv7ZflF0TEaTAoU7tOzg=
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||||
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1QAp/SlnNrZhI=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
@ -289,6 +308,7 @@ github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3 h1:jNYP
|
|||||||
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
|
github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0=
|
||||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
|
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
|
||||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
@ -327,6 +347,7 @@ github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP
|
|||||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
||||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||||
github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ=
|
github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ=
|
||||||
github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU=
|
github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU=
|
||||||
@ -369,6 +390,7 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
|
|||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
github.com/mozilla/tls-observatory v0.0.0-20200317151703-4fa42e1c2dee/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||||
@ -460,6 +482,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
|
|||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY=
|
github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY=
|
||||||
github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
|
github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI=
|
||||||
@ -498,6 +521,8 @@ github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s
|
|||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/suzerain-io/placeholder-name-api v0.0.0-20200714184318-8ad91581433a h1:ycG6TufZM7ZDrgBklkXEMauvSyh44JQDvSUdJawAjGA=
|
github.com/suzerain-io/placeholder-name-api v0.0.0-20200714184318-8ad91581433a h1:ycG6TufZM7ZDrgBklkXEMauvSyh44JQDvSUdJawAjGA=
|
||||||
github.com/suzerain-io/placeholder-name-api v0.0.0-20200714184318-8ad91581433a/go.mod h1:bNHheAnmAISdW/ZYTnhCmg8QQKwA5WD64ZvPdsTrWjw=
|
github.com/suzerain-io/placeholder-name-api v0.0.0-20200714184318-8ad91581433a/go.mod h1:bNHheAnmAISdW/ZYTnhCmg8QQKwA5WD64ZvPdsTrWjw=
|
||||||
|
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200714203950-a414963b4f95 h1:O1aoszdMJEekLXD7pNWP23vq0g8eXLp40AQgY8Hj+Sw=
|
||||||
|
github.com/suzerain-io/placeholder-name-client-go v0.0.0-20200714203950-a414963b4f95/go.mod h1:NwirXEgd1VaA+6R0W7PYL/ae6wfPT3vA+tu7POAArjU=
|
||||||
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ=
|
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ=
|
||||||
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
|
github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM=
|
||||||
github.com/tetafro/godot v0.4.2 h1:Dib7un+rYJFUi8vN0Bk6EHheKy6fv6ZzFURHw75g6m8=
|
github.com/tetafro/godot v0.4.2 h1:Dib7un+rYJFUi8vN0Bk6EHheKy6fv6ZzFURHw75g6m8=
|
||||||
@ -505,6 +530,7 @@ github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQx
|
|||||||
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q=
|
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q=
|
||||||
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
|
||||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||||
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As=
|
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As=
|
||||||
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
|
github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig=
|
||||||
@ -520,6 +546,7 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
|
|||||||
github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E=
|
github.com/valyala/fasthttp v1.12.0/go.mod h1:229t1eWu9UXTPmoUkbpN/fctKPBY4IJoFXQnxHGXy6E=
|
||||||
github.com/valyala/quicktemplate v1.5.0/go.mod h1:v7yYWpBEiutDyNfVaph6oC/yKwejzVyTX/2cwwHxyok=
|
github.com/valyala/quicktemplate v1.5.0/go.mod h1:v7yYWpBEiutDyNfVaph6oC/yKwejzVyTX/2cwwHxyok=
|
||||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||||
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
|
||||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@ -527,14 +554,19 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||||
|
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
|
go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f h1:pBCD+Z7cy5WPTq+R6MmJJvDRpn88cp7bmTypBsn91g4=
|
||||||
go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8=
|
go.etcd.io/etcd v0.5.0-alpha.5.0.20200520232829-54ba9589114f/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8=
|
||||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
@ -650,6 +682,8 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
|
|||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
|
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s=
|
||||||
|
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -759,6 +793,7 @@ gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
|||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
@ -801,6 +836,7 @@ k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A=
|
|||||||
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
|
||||||
k8s.io/kube-aggregator v0.19.0-rc.0 h1:+u9y1c0R2GF8fuaEnlJrdUtxoEmQOON98oatycSquOA=
|
k8s.io/kube-aggregator v0.19.0-rc.0 h1:+u9y1c0R2GF8fuaEnlJrdUtxoEmQOON98oatycSquOA=
|
||||||
k8s.io/kube-aggregator v0.19.0-rc.0/go.mod h1:DCq8Korz9XUEZVsq0wAGIAyJW79xdcYhIBtvWNTsTkc=
|
k8s.io/kube-aggregator v0.19.0-rc.0/go.mod h1:DCq8Korz9XUEZVsq0wAGIAyJW79xdcYhIBtvWNTsTkc=
|
||||||
|
k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9 h1:5NC2ITmvg8RoxoH0wgmL4zn4VZqXGsKbxrikjaQx6s4=
|
||||||
k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9/go.mod h1:bfCVj+qXcEaE5SCvzBaqpOySr6tuCcpPKqF6HD8nyCw=
|
k8s.io/kube-openapi v0.0.0-20200427153329-656914f816f9/go.mod h1:bfCVj+qXcEaE5SCvzBaqpOySr6tuCcpPKqF6HD8nyCw=
|
||||||
k8s.io/kube-openapi v0.0.0-20200615155156-dffdd1682719 h1:n/ElZyI1dzFPXKS8nZMw8wozBUz7vEfL0Ja7jN2rSvA=
|
k8s.io/kube-openapi v0.0.0-20200615155156-dffdd1682719 h1:n/ElZyI1dzFPXKS8nZMw8wozBUz7vEfL0Ja7jN2rSvA=
|
||||||
k8s.io/kube-openapi v0.0.0-20200615155156-dffdd1682719/go.mod h1:bfCVj+qXcEaE5SCvzBaqpOySr6tuCcpPKqF6HD8nyCw=
|
k8s.io/kube-openapi v0.0.0-20200615155156-dffdd1682719/go.mod h1:bfCVj+qXcEaE5SCvzBaqpOySr6tuCcpPKqF6HD8nyCw=
|
||||||
|
@ -71,7 +71,7 @@ kube::version::get_version_vars() {
|
|||||||
# compatible semantic version that looks something like this:
|
# compatible semantic version that looks something like this:
|
||||||
# v1.1.0-alpha.0.6+84c76d1142ea4d
|
# v1.1.0-alpha.0.6+84c76d1142ea4d
|
||||||
#
|
#
|
||||||
# TODO: We continue calling this "git version" because so many
|
# xTODO: We continue calling this "git version" because so many
|
||||||
# downstream consumers are expecting it there.
|
# downstream consumers are expecting it there.
|
||||||
#
|
#
|
||||||
# These regexes are painful enough in sed...
|
# These regexes are painful enough in sed...
|
||||||
|
132
pkg/apiserver/apiserver.go
Normal file
132
pkg/apiserver/apiserver.go
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package apiserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
|
"k8s.io/client-go/pkg/version"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
placeholderapi "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder"
|
||||||
|
placeholderv1alpha1 "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1"
|
||||||
|
"github.com/suzerain-io/placeholder-name/pkg/registry/loginrequest"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//nolint: gochecknoglobals
|
||||||
|
scheme = runtime.NewScheme()
|
||||||
|
//nolint: gochecknoglobals
|
||||||
|
//nolint: golint
|
||||||
|
Codecs = serializer.NewCodecFactory(scheme)
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint: gochecknoinits
|
||||||
|
func init() {
|
||||||
|
utilruntime.Must(placeholderv1alpha1.AddToScheme(scheme))
|
||||||
|
utilruntime.Must(placeholderapi.AddToScheme(scheme))
|
||||||
|
|
||||||
|
// add the options to empty v1
|
||||||
|
metav1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"})
|
||||||
|
|
||||||
|
unversioned := schema.GroupVersion{Group: "", Version: "v1"}
|
||||||
|
scheme.AddUnversionedTypes(unversioned,
|
||||||
|
&metav1.Status{},
|
||||||
|
&metav1.APIVersions{},
|
||||||
|
&metav1.APIGroupList{},
|
||||||
|
&metav1.APIGroup{},
|
||||||
|
&metav1.APIResourceList{},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
GenericConfig *genericapiserver.RecommendedConfig
|
||||||
|
ExtraConfig ExtraConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExtraConfig struct {
|
||||||
|
Webhook authenticator.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlaceHolderServer struct {
|
||||||
|
GenericAPIServer *genericapiserver.GenericAPIServer
|
||||||
|
}
|
||||||
|
|
||||||
|
type completedConfig struct {
|
||||||
|
GenericConfig genericapiserver.CompletedConfig
|
||||||
|
ExtraConfig *ExtraConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompletedConfig struct {
|
||||||
|
// Embed a private pointer that cannot be instantiated outside of this package.
|
||||||
|
*completedConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
|
||||||
|
func (c *Config) Complete() CompletedConfig {
|
||||||
|
completedCfg := completedConfig{
|
||||||
|
c.GenericConfig.Complete(),
|
||||||
|
&c.ExtraConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
versionInfo := version.Get()
|
||||||
|
completedCfg.GenericConfig.Version = &versionInfo
|
||||||
|
|
||||||
|
return CompletedConfig{completedConfig: &completedCfg}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new instance of AdmissionServer from the given config.
|
||||||
|
func (c completedConfig) New() (*PlaceHolderServer, error) {
|
||||||
|
genericServer, err := c.GenericConfig.New("place-holder-server", genericapiserver.NewEmptyDelegate()) // completion is done in Complete, no need for a second time
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("completion error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &PlaceHolderServer{
|
||||||
|
GenericAPIServer: genericServer,
|
||||||
|
}
|
||||||
|
|
||||||
|
gvr := placeholderv1alpha1.SchemeGroupVersion.WithResource("loginrequests")
|
||||||
|
|
||||||
|
apiGroupInfo := genericapiserver.APIGroupInfo{
|
||||||
|
PrioritizedVersions: []schema.GroupVersion{gvr.GroupVersion()},
|
||||||
|
VersionedResourcesStorageMap: map[string]map[string]rest.Storage{},
|
||||||
|
OptionsExternalVersion: &schema.GroupVersion{Version: "v1"},
|
||||||
|
Scheme: scheme,
|
||||||
|
ParameterCodec: metav1.ParameterCodec,
|
||||||
|
NegotiatedSerializer: Codecs,
|
||||||
|
}
|
||||||
|
|
||||||
|
loginRequestStorage := loginrequest.NewREST(c.ExtraConfig.Webhook)
|
||||||
|
|
||||||
|
v1alpha1Storage, ok := apiGroupInfo.VersionedResourcesStorageMap[gvr.Version]
|
||||||
|
if !ok {
|
||||||
|
v1alpha1Storage = map[string]rest.Storage{}
|
||||||
|
}
|
||||||
|
v1alpha1Storage[gvr.Resource] = loginRequestStorage
|
||||||
|
apiGroupInfo.VersionedResourcesStorageMap[gvr.Version] = v1alpha1Storage
|
||||||
|
|
||||||
|
if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
|
||||||
|
return nil, fmt.Errorf("install API group error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.GenericAPIServer.AddPostStartHookOrDie("place-holder-post-start-hook",
|
||||||
|
func(context genericapiserver.PostStartHookContext) error {
|
||||||
|
klog.InfoS("post start hook", "foo", "bar")
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
114
pkg/registry/loginrequest/rest.go
Normal file
114
pkg/registry/loginrequest/rest.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package loginrequest provides REST functionality for the LoginRequest resource.
|
||||||
|
package loginrequest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
|
||||||
|
placeholderapi "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ rest.Creater = &REST{}
|
||||||
|
_ rest.NamespaceScopedStrategy = &REST{}
|
||||||
|
_ rest.Scoper = &REST{}
|
||||||
|
_ rest.Storage = &REST{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewREST(webhook authenticator.Token) *REST {
|
||||||
|
return &REST{
|
||||||
|
webhook: webhook,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type REST struct {
|
||||||
|
webhook authenticator.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *REST) New() runtime.Object {
|
||||||
|
return &placeholderapi.LoginRequest{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *REST) NamespaceScoped() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *REST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
|
||||||
|
loginRequest, ok := obj.(*placeholderapi.LoginRequest)
|
||||||
|
if !ok {
|
||||||
|
return nil, apierrors.NewBadRequest(fmt.Sprintf("not a LoginRequest: %#v", obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO refactor all validation checks into a validation function in another package (e.g. see subjectaccessreqview api in k8s)
|
||||||
|
|
||||||
|
// TODO also validate .spec.type
|
||||||
|
token := loginRequest.Spec.Token
|
||||||
|
if token == nil || len(token.Value) == 0 {
|
||||||
|
errs := field.ErrorList{field.Required(field.NewPath("spec", "token", "value"), "token must be supplied")}
|
||||||
|
return nil, apierrors.NewInvalid(placeholderapi.Kind(loginRequest.Kind), loginRequest.Name, errs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// let dynamic admission webhooks have a chance to validate (but not mutate) as well
|
||||||
|
// TODO Are we okay with admission webhooks being able to see tokens? Maybe strip token out before passing obj to createValidation.
|
||||||
|
// Since we are an aggregated API, we should investigate to see if the kube API server is already invoking admission hooks for us.
|
||||||
|
// Even if it is, its okay to call it again here. If the kube API server is already calling the webhooks and passing
|
||||||
|
// the token, then there is probably no reason for us to avoid passing the token when we call the webhooks here, since
|
||||||
|
// they already got the token.
|
||||||
|
if createValidation != nil {
|
||||||
|
if err := createValidation(ctx, obj.DeepCopyObject()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// just a sanity check, not sure how to honor a dry run on a virtual API
|
||||||
|
if options != nil {
|
||||||
|
if len(options.DryRun) != 0 {
|
||||||
|
errs := field.ErrorList{field.NotSupported(field.NewPath("dryRun"), options.DryRun, nil)}
|
||||||
|
return nil, apierrors.NewInvalid(placeholderapi.Kind(loginRequest.Kind), loginRequest.Name, errs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the incoming context could have an audience attached to it technically
|
||||||
|
// sine we do not want to handle audiences right now, do not pass it through directly
|
||||||
|
// instead we just propagate cancellation of the parent context
|
||||||
|
cancelCtx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
cancel()
|
||||||
|
case <-cancelCtx.Done():
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// TODO do the actual business logic of this endpoint here
|
||||||
|
|
||||||
|
_, _, err := r.webhook.AuthenticateToken(cancelCtx, token.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("authenticate token failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make a new object so that we do not return the original token in the response
|
||||||
|
out := &placeholderapi.LoginRequest{
|
||||||
|
Status: placeholderapi.LoginRequestStatus{
|
||||||
|
ExpirationTimestamp: nil,
|
||||||
|
Token: "snorlax",
|
||||||
|
ClientCertificateData: "",
|
||||||
|
ClientKeyData: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
240
pkg/registry/loginrequest/rest_test.go
Normal file
240
pkg/registry/loginrequest/rest_test.go
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package loginrequest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||||
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
|
||||||
|
placeholderapi "github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey struct{}
|
||||||
|
|
||||||
|
type FakeToken struct {
|
||||||
|
calledWithToken string
|
||||||
|
calledWithContext context.Context
|
||||||
|
timeout time.Duration
|
||||||
|
reachedTimeout bool
|
||||||
|
cancelled bool
|
||||||
|
webhookStartedRunningNotificationChan chan bool
|
||||||
|
returnErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeToken) AuthenticateToken(ctx context.Context, token string) (*authenticator.Response, bool, error) {
|
||||||
|
f.calledWithToken = token
|
||||||
|
f.calledWithContext = ctx
|
||||||
|
if f.webhookStartedRunningNotificationChan != nil {
|
||||||
|
f.webhookStartedRunningNotificationChan <- true
|
||||||
|
}
|
||||||
|
afterCh := time.After(f.timeout)
|
||||||
|
select {
|
||||||
|
case <-afterCh:
|
||||||
|
f.reachedTimeout = true
|
||||||
|
case <-ctx.Done():
|
||||||
|
f.cancelled = true
|
||||||
|
}
|
||||||
|
return &authenticator.Response{}, true, f.returnErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func callCreate(ctx context.Context, storage *REST, loginRequest *placeholderapi.LoginRequest) (runtime.Object, error) {
|
||||||
|
return storage.Create(
|
||||||
|
ctx,
|
||||||
|
loginRequest,
|
||||||
|
rest.ValidateAllObjectFunc,
|
||||||
|
&metav1.CreateOptions{
|
||||||
|
DryRun: []string{},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func validLoginRequest() *placeholderapi.LoginRequest {
|
||||||
|
return loginRequest(placeholderapi.LoginRequestSpec{
|
||||||
|
Type: placeholderapi.TokenLoginCredentialType,
|
||||||
|
Token: &placeholderapi.LoginRequestTokenCredential{Value: "a token"},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func loginRequest(spec placeholderapi.LoginRequestSpec) *placeholderapi.LoginRequest {
|
||||||
|
return &placeholderapi.LoginRequest{
|
||||||
|
TypeMeta: metav1.TypeMeta{},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "request name",
|
||||||
|
},
|
||||||
|
Spec: spec,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSucceedsWhenGivenAToken(t *testing.T) {
|
||||||
|
webhook := FakeToken{}
|
||||||
|
storage := NewREST(&webhook)
|
||||||
|
requestToken := "a token"
|
||||||
|
response, err := callCreate(context.Background(), storage, loginRequest(placeholderapi.LoginRequestSpec{
|
||||||
|
Type: placeholderapi.TokenLoginCredentialType,
|
||||||
|
Token: &placeholderapi.LoginRequestTokenCredential{Value: requestToken},
|
||||||
|
}))
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, response, &placeholderapi.LoginRequest{
|
||||||
|
Status: placeholderapi.LoginRequestStatus{
|
||||||
|
ExpirationTimestamp: nil,
|
||||||
|
Token: "snorlax",
|
||||||
|
ClientCertificateData: "",
|
||||||
|
ClientKeyData: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Equal(t, requestToken, webhook.calledWithToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateDoesNotPassAdditionalContextInfoToTheWebhook(t *testing.T) {
|
||||||
|
webhook := FakeToken{}
|
||||||
|
storage := NewREST(&webhook)
|
||||||
|
ctx := context.WithValue(context.Background(), contextKey{}, "context-value")
|
||||||
|
|
||||||
|
_, err := callCreate(ctx, storage, validLoginRequest())
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Nil(t, webhook.calledWithContext.Value("context-key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateCancelsTheWebhookInvocationWhenTheCallToCreateIsCancelledItself(t *testing.T) {
|
||||||
|
webhookStartedRunningNotificationChan := make(chan bool)
|
||||||
|
webhook := FakeToken{
|
||||||
|
timeout: time.Second * 2,
|
||||||
|
webhookStartedRunningNotificationChan: webhookStartedRunningNotificationChan,
|
||||||
|
}
|
||||||
|
storage := NewREST(&webhook)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
c := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
_, err := callCreate(ctx, storage, validLoginRequest())
|
||||||
|
c <- true
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
require.False(t, webhook.cancelled)
|
||||||
|
require.False(t, webhook.reachedTimeout)
|
||||||
|
<-webhookStartedRunningNotificationChan // wait long enough to make sure that the webhook has started
|
||||||
|
cancel() // cancel the context that was passed to storage.Create() above
|
||||||
|
<-c // wait for the above call to storage.Create() to be finished
|
||||||
|
require.True(t, webhook.cancelled)
|
||||||
|
require.False(t, webhook.reachedTimeout)
|
||||||
|
require.Equal(t, context.Canceled, webhook.calledWithContext.Err()) // the inner context is cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateAllowsTheWebhookInvocationToFinishWhenTheCallToCreateIsNotCancelledItself(t *testing.T) {
|
||||||
|
webhookStartedRunningNotificationChan := make(chan bool)
|
||||||
|
webhook := FakeToken{
|
||||||
|
timeout: 0,
|
||||||
|
webhookStartedRunningNotificationChan: webhookStartedRunningNotificationChan,
|
||||||
|
}
|
||||||
|
storage := NewREST(&webhook)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c := make(chan bool)
|
||||||
|
go func() {
|
||||||
|
_, err := callCreate(ctx, storage, validLoginRequest())
|
||||||
|
c <- true
|
||||||
|
require.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
require.False(t, webhook.cancelled)
|
||||||
|
require.False(t, webhook.reachedTimeout)
|
||||||
|
<-webhookStartedRunningNotificationChan // wait long enough to make sure that the webhook has started
|
||||||
|
<-c // wait for the above call to storage.Create() to be finished
|
||||||
|
require.False(t, webhook.cancelled)
|
||||||
|
require.True(t, webhook.reachedTimeout)
|
||||||
|
require.Equal(t, context.Canceled, webhook.calledWithContext.Err()) // the inner context is cancelled (in this case by the "defer")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateFailsWhenWebhookFails(t *testing.T) {
|
||||||
|
webhook := FakeToken{
|
||||||
|
returnErr: errors.New("some webhook error"),
|
||||||
|
}
|
||||||
|
storage := NewREST(&webhook)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, err := callCreate(ctx, storage, validLoginRequest())
|
||||||
|
require.EqualError(t, err, "authenticate token failed: some webhook error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateFailsWhenGivenTheWrongInputType(t *testing.T) {
|
||||||
|
notALoginRequest := runtime.Unknown{}
|
||||||
|
response, err := NewREST(&FakeToken{}).Create(
|
||||||
|
genericapirequest.NewContext(),
|
||||||
|
¬ALoginRequest,
|
||||||
|
rest.ValidateAllObjectFunc,
|
||||||
|
&metav1.CreateOptions{})
|
||||||
|
|
||||||
|
require.Nil(t, response)
|
||||||
|
require.True(t, apierrors.IsBadRequest(err))
|
||||||
|
var status apierrors.APIStatus
|
||||||
|
errors.As(err, &status)
|
||||||
|
require.Contains(t, status.Status().Message, "not a LoginRequest")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateFailsWhenTokenIsNilInRequest(t *testing.T) {
|
||||||
|
storage := NewREST(&FakeToken{})
|
||||||
|
response, err := callCreate(context.Background(), storage, loginRequest(placeholderapi.LoginRequestSpec{
|
||||||
|
Type: placeholderapi.TokenLoginCredentialType,
|
||||||
|
Token: nil,
|
||||||
|
}))
|
||||||
|
|
||||||
|
require.Nil(t, response)
|
||||||
|
require.True(t, apierrors.IsInvalid(err))
|
||||||
|
var status apierrors.APIStatus
|
||||||
|
errors.As(err, &status)
|
||||||
|
require.Equal(t,
|
||||||
|
`.placeholder.suzerain-io.github.io "request name" is invalid: spec.token.value: Required value: token must be supplied`,
|
||||||
|
status.Status().Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateFailsWhenTokenValueIsEmptyInRequest(t *testing.T) {
|
||||||
|
storage := NewREST(&FakeToken{})
|
||||||
|
response, err := callCreate(context.Background(), storage, loginRequest(placeholderapi.LoginRequestSpec{
|
||||||
|
Type: placeholderapi.TokenLoginCredentialType,
|
||||||
|
Token: &placeholderapi.LoginRequestTokenCredential{Value: ""},
|
||||||
|
}))
|
||||||
|
|
||||||
|
require.Nil(t, response)
|
||||||
|
require.True(t, apierrors.IsInvalid(err))
|
||||||
|
var status apierrors.APIStatus
|
||||||
|
errors.As(err, &status)
|
||||||
|
require.Equal(t,
|
||||||
|
`.placeholder.suzerain-io.github.io "request name" is invalid: spec.token.value: Required value: token must be supplied`,
|
||||||
|
status.Status().Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateFailsWhenRequestOptionsDryRunIsNotEmpty(t *testing.T) {
|
||||||
|
response, err := NewREST(&FakeToken{}).Create(
|
||||||
|
genericapirequest.NewContext(),
|
||||||
|
validLoginRequest(),
|
||||||
|
rest.ValidateAllObjectFunc,
|
||||||
|
&metav1.CreateOptions{
|
||||||
|
DryRun: []string{"some dry run flag"},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.Nil(t, response)
|
||||||
|
require.True(t, apierrors.IsInvalid(err))
|
||||||
|
var status apierrors.APIStatus
|
||||||
|
errors.As(err, &status)
|
||||||
|
require.Equal(t,
|
||||||
|
`.placeholder.suzerain-io.github.io "request name" is invalid: dryRun: Unsupported value: []string{"some dry run flag"}`,
|
||||||
|
status.Status().Message)
|
||||||
|
}
|
184
test/integration/loginrequest_test.go
Normal file
184
test/integration/loginrequest_test.go
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 VMware, Inc.
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
|
"github.com/suzerain-io/placeholder-name-api/pkg/apis/placeholder/v1alpha1"
|
||||||
|
"github.com/suzerain-io/placeholder-name/test/library"
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeRequest(t *testing.T, spec v1alpha1.LoginRequestSpec) (*v1alpha1.LoginRequest, error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
client := library.NewPlaceholderNameClientset(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
return client.PlaceholderV1alpha1().LoginRequests().Create(ctx, &v1alpha1.LoginRequest{
|
||||||
|
TypeMeta: metav1.TypeMeta{},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{},
|
||||||
|
Spec: spec,
|
||||||
|
}, metav1.CreateOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSuccessfulLoginRequest(t *testing.T) {
|
||||||
|
tmcClusterToken := os.Getenv("PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN")
|
||||||
|
require.NotEmptyf(t, tmcClusterToken, "must specify PLACEHOLDER_NAME_TMC_CLUSTER_TOKEN env var for integration tests")
|
||||||
|
|
||||||
|
response, err := makeRequest(t, v1alpha1.LoginRequestSpec{
|
||||||
|
Token: &v1alpha1.LoginRequestTokenCredential{Value: tmcClusterToken},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Empty(t, response.Spec)
|
||||||
|
require.NotEmpty(t, response.Status.Token)
|
||||||
|
require.Empty(t, response.Status.ClientCertificateData)
|
||||||
|
require.Empty(t, response.Status.ClientKeyData)
|
||||||
|
require.Nil(t, response.Status.ExpirationTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginRequest_ShouldFailWhenRequestDoesNotIncludeToken(t *testing.T) {
|
||||||
|
_, err := makeRequest(t, v1alpha1.LoginRequestSpec{})
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
statusError, isStatus := err.(*errors.StatusError)
|
||||||
|
require.True(t, isStatus)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(statusError.ErrStatus.Details.Causes))
|
||||||
|
cause := statusError.ErrStatus.Details.Causes[0]
|
||||||
|
require.Equal(t, metav1.CauseType("FieldValueRequired"), cause.Type)
|
||||||
|
require.Equal(t, "Required value: token must be supplied", cause.Message)
|
||||||
|
require.Equal(t, "spec.token.value", cause.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetDiscovery(t *testing.T) {
|
||||||
|
client := library.NewPlaceholderNameClientset(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result, err := client.Discovery().RESTClient().Get().Do(ctx).Raw()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var parsedResult map[string]interface{}
|
||||||
|
err = json.Unmarshal(result, &parsedResult)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Contains(t, parsedResult["paths"], "/apis/placeholder.suzerain-io.github.io")
|
||||||
|
require.Contains(t, parsedResult["paths"], "/apis/placeholder.suzerain-io.github.io/v1alpha1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAPIResourceList(t *testing.T) {
|
||||||
|
var expectedAPIResourceList = `{
|
||||||
|
"kind": "APIResourceList",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"groupVersion": "placeholder.suzerain-io.github.io/v1alpha1",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"name": "loginrequests",
|
||||||
|
"singularName": "",
|
||||||
|
"namespaced": false,
|
||||||
|
"kind": "LoginRequest",
|
||||||
|
"verbs": [
|
||||||
|
"create"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
client := library.NewPlaceholderNameClientset(t)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
result, err := client.PlaceholderV1alpha1().RESTClient().Get().Do(ctx).Raw()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.JSONEq(t, expectedAPIResourceList, string(result))
|
||||||
|
|
||||||
|
// proposed:
|
||||||
|
|
||||||
|
groups, resources, err := client.Discovery().ServerGroupsAndResources()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
groupName := "placeholder.suzerain-io.github.io"
|
||||||
|
actualGroup := findGroup(groupName, groups)
|
||||||
|
require.NotNil(t, actualGroup)
|
||||||
|
|
||||||
|
expectedGroup := &metav1.APIGroup{
|
||||||
|
Name: "placeholder.suzerain-io.github.io",
|
||||||
|
Versions: []metav1.GroupVersionForDiscovery{
|
||||||
|
metav1.GroupVersionForDiscovery{
|
||||||
|
GroupVersion: "placeholder.suzerain-io.github.io/v1alpha1",
|
||||||
|
Version: "v1alpha1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PreferredVersion: metav1.GroupVersionForDiscovery{
|
||||||
|
GroupVersion: "placeholder.suzerain-io.github.io/v1alpha1",
|
||||||
|
Version: "v1alpha1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Equal(t, expectedGroup, actualGroup)
|
||||||
|
|
||||||
|
resourceGroupVersion := "placeholder.suzerain-io.github.io/v1alpha1"
|
||||||
|
actualResources := findResources(resourceGroupVersion, resources)
|
||||||
|
require.NotNil(t, actualResources)
|
||||||
|
|
||||||
|
expectedResources := &metav1.APIResourceList{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
Kind: "APIResourceList",
|
||||||
|
APIVersion: "v1",
|
||||||
|
},
|
||||||
|
GroupVersion: "placeholder.suzerain-io.github.io/v1alpha1",
|
||||||
|
APIResources: []metav1.APIResource{
|
||||||
|
metav1.APIResource{
|
||||||
|
Name: "loginrequests",
|
||||||
|
Kind: "LoginRequest",
|
||||||
|
SingularName: "", // TODO(akeesler): what should this be?
|
||||||
|
Verbs: metav1.Verbs([]string{
|
||||||
|
"create",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Equal(t, expectedResources, actualResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetAPIVersion(t *testing.T) {
|
||||||
|
client := library.NewPlaceholderNameClientset(t)
|
||||||
|
|
||||||
|
version, err := client.Discovery().ServerVersion()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, version) // TODO(akeesler: what can we assert here?
|
||||||
|
}
|
||||||
|
|
||||||
|
func findGroup(name string, groups []*metav1.APIGroup) *metav1.APIGroup {
|
||||||
|
for _, group := range groups {
|
||||||
|
if group.Name == name {
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findResources(groupVersion string, resources []*metav1.APIResourceList) *metav1.APIResourceList {
|
||||||
|
for _, resource := range resources {
|
||||||
|
if resource.GroupVersion == groupVersion {
|
||||||
|
return resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -12,6 +12,8 @@ import (
|
|||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
|
||||||
|
placeholdernameclientset "github.com/suzerain-io/placeholder-name-client-go/pkg/generated/clientset/versioned"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewClientConfig(t *testing.T) *rest.Config {
|
func NewClientConfig(t *testing.T) *rest.Config {
|
||||||
@ -30,3 +32,9 @@ func NewClientset(t *testing.T) kubernetes.Interface {
|
|||||||
|
|
||||||
return kubernetes.NewForConfigOrDie(NewClientConfig(t))
|
return kubernetes.NewForConfigOrDie(NewClientConfig(t))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewPlaceholderNameClientset(t *testing.T) placeholdernameclientset.Interface {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
return placeholdernameclientset.NewForConfigOrDie(NewClientConfig(t))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user