Skip to content

Commit

Permalink
add DefaultComponentGlobalsRegistry flags in ServerRunOptions
Browse files Browse the repository at this point in the history
Signed-off-by: Siyuan Zhang <sizhang@google.com>
  • Loading branch information
siyuanfoundation committed Jun 25, 2024
1 parent 4352c4a commit 379676c
Show file tree
Hide file tree
Showing 36 changed files with 731 additions and 282 deletions.
6 changes: 2 additions & 4 deletions cmd/kube-apiserver/app/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import (
utilnet "k8s.io/apimachinery/pkg/util/net"
cliflag "k8s.io/component-base/cli/flag"

utilversion "k8s.io/apiserver/pkg/util/version"
"k8s.io/component-base/featuregate"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/cluster/ports"
controlplaneapiserver "k8s.io/kubernetes/pkg/controlplane/apiserver/options"
Expand Down Expand Up @@ -66,9 +64,9 @@ type Extra struct {
}

// NewServerRunOptions creates and returns ServerRunOptions according to the given featureGate and effectiveVersion of the server binary to run.
func NewServerRunOptions(featureGate featuregate.FeatureGate, effectiveVersion utilversion.EffectiveVersion) *ServerRunOptions {
func NewServerRunOptions() *ServerRunOptions {
s := ServerRunOptions{
Options: controlplaneapiserver.NewOptions(featureGate, effectiveVersion),
Options: controlplaneapiserver.NewOptions(),
CloudProvider: kubeoptions.NewCloudProviderOptions(),

Extra: Extra{
Expand Down
24 changes: 12 additions & 12 deletions cmd/kube-apiserver/app/options/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ import (
)

func TestAddFlags(t *testing.T) {
componentGlobalsRegistry := utilversion.DefaultComponentGlobalsRegistry
t.Cleanup(func() {
componentGlobalsRegistry.Reset()
})
fs := pflag.NewFlagSet("addflagstest", pflag.PanicOnError)

featureGate := featuregate.NewFeatureGate()
componentRegistry := utilversion.NewComponentGlobalsRegistry()
effectiveVersion := utilversion.NewEffectiveVersion("1.32")
utilruntime.Must(componentRegistry.Register("test", effectiveVersion, featureGate))
s := NewServerRunOptions(featureGate, effectiveVersion)
utilruntime.Must(componentGlobalsRegistry.Register("test", utilversion.NewEffectiveVersion("1.32"), featuregate.NewFeatureGate()))
s := NewServerRunOptions()
for _, f := range s.Flags().FlagSets {
fs.AddFlagSet(f)
}
componentRegistry.AddFlags(fs)

args := []string{
"--enable-admission-plugins=AlwaysDeny",
Expand Down Expand Up @@ -133,7 +133,7 @@ func TestAddFlags(t *testing.T) {
"--emulated-version=test=1.31",
}
fs.Parse(args)
utilruntime.Must(componentRegistry.Set())
utilruntime.Must(componentGlobalsRegistry.Set())

// This is a snapshot of expected options parsed by args.
expected := &ServerRunOptions{
Expand All @@ -147,8 +147,8 @@ func TestAddFlags(t *testing.T) {
MinRequestTimeout: 1800,
JSONPatchMaxCopyBytes: int64(3 * 1024 * 1024),
MaxRequestBodyBytes: int64(3 * 1024 * 1024),
FeatureGate: featureGate,
EffectiveVersion: effectiveVersion,
ComponentGlobalsRegistry: componentGlobalsRegistry,
ComponentName: utilversion.DefaultKubeComponent,
},
Admission: &kubeoptions.AdmissionOptions{
GenericAdmission: &apiserveroptions.AdmissionOptions{
Expand Down Expand Up @@ -350,8 +350,8 @@ func TestAddFlags(t *testing.T) {
if !reflect.DeepEqual(expected, s) {
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{})))
}

if s.GenericServerRunOptions.EffectiveVersion.EmulationVersion().String() != "1.31" {
t.Errorf("Got emulation version %s, wanted %s", s.GenericServerRunOptions.EffectiveVersion.EmulationVersion().String(), "1.31")
testEffectiveVersion := s.GenericServerRunOptions.ComponentGlobalsRegistry.EffectiveVersionFor("test")
if testEffectiveVersion.EmulationVersion().String() != "1.31" {
t.Errorf("Got emulation version %s, wanted %s", testEffectiveVersion.EmulationVersion().String(), "1.31")
}
}
10 changes: 10 additions & 0 deletions cmd/kube-apiserver/app/options/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
netutils "k8s.io/utils/net"

"k8s.io/apimachinery/pkg/util/version"
controlplaneapiserver "k8s.io/kubernetes/pkg/controlplane/apiserver/options"
"k8s.io/kubernetes/pkg/controlplane/reconcilers"
"k8s.io/kubernetes/pkg/features"
Expand Down Expand Up @@ -139,5 +140,14 @@ func (s CompletedOptions) Validate() []error {
errs = append(errs, fmt.Errorf("--apiserver-count should be a positive number, but value '%d' provided", s.MasterCount))
}

// TODO: remove in 1.32
// emulationVersion is introduced in 1.31, so it is only allowed to be equal to the binary version at 1.31.
effectiveVersion := s.GenericServerRunOptions.ComponentGlobalsRegistry.EffectiveVersionFor(s.GenericServerRunOptions.ComponentName)
binaryVersion := version.MajorMinor(effectiveVersion.BinaryVersion().Major(), effectiveVersion.BinaryVersion().Minor())
if binaryVersion.EqualTo(version.MajorMinor(1, 31)) && !effectiveVersion.EmulationVersion().EqualTo(binaryVersion) {
errs = append(errs, fmt.Errorf("emulation version needs to be equal to binary version(%s) in compatibility-version alpha, got %s",
binaryVersion.String(), effectiveVersion.EmulationVersion().String()))
}

return errs
}
6 changes: 2 additions & 4 deletions cmd/kube-apiserver/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ func init() {

// NewAPIServerCommand creates a *cobra.Command object with default parameters
func NewAPIServerCommand() *cobra.Command {
effectiveVersion, featureGate := utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(
_, featureGate := utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(
utilversion.DefaultKubeComponent, utilversion.DefaultBuildEffectiveVersion(), utilfeature.DefaultMutableFeatureGate)
s := options.NewServerRunOptions(featureGate, effectiveVersion)
s := options.NewServerRunOptions()

cmd := &cobra.Command{
Use: "kube-apiserver",
Expand Down Expand Up @@ -124,8 +124,6 @@ cluster's shared state through which all other components interact.`,
fs := cmd.Flags()
namedFlagSets := s.Flags()
verflag.AddFlags(namedFlagSets.FlagSet("global"))
utilversion.DefaultComponentGlobalsRegistry.AddFlags(namedFlagSets.FlagSet("global"))

globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name(), logs.SkipLoggingConfigurationFlags())
options.AddCustomGlobalFlags(namedFlagSets.FlagSet("generic"))
for _, f := range namedFlagSets.FlagSets {
Expand Down
6 changes: 4 additions & 2 deletions cmd/kube-apiserver/app/testing/testserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import (
clientgotransport "k8s.io/client-go/transport"
"k8s.io/client-go/util/cert"
"k8s.io/client-go/util/keyutil"
featuregatetesting "k8s.io/component-base/featuregate/testing"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/klog/v2"
"k8s.io/kube-aggregator/pkg/apiserver"
Expand Down Expand Up @@ -187,15 +188,16 @@ func StartTestServer(t ktesting.TB, instanceOptions *TestServerInstanceOptions,
if instanceOptions.BinaryVersion != "" {
effectiveVersion = utilversion.NewEffectiveVersion(instanceOptions.BinaryVersion)
}
// need to call SetFeatureGateEmulationVersionDuringTest to reset the feature gate emulation version at the end of the test.
featuregatetesting.SetFeatureGateEmulationVersionDuringTest(t, featureGate, effectiveVersion.EmulationVersion())
utilversion.DefaultComponentGlobalsRegistry.Reset()
utilruntime.Must(utilversion.DefaultComponentGlobalsRegistry.Register(utilversion.DefaultKubeComponent, effectiveVersion, featureGate))

s := options.NewServerRunOptions(featureGate, effectiveVersion)
s := options.NewServerRunOptions()

for _, f := range s.Flags().FlagSets {
fs.AddFlagSet(f)
}
utilversion.DefaultComponentGlobalsRegistry.AddFlags(fs)

s.SecureServing.Listener, s.SecureServing.BindPort, err = createLocalhostListenerOnFreePort()
if err != nil {
Expand Down
6 changes: 1 addition & 5 deletions pkg/controlplane/apiserver/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
apiserveroptions "k8s.io/apiserver/pkg/server/options"
utilversion "k8s.io/apiserver/pkg/util/version"
"k8s.io/component-base/featuregate"
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/controlplane/apiserver/options"
Expand All @@ -34,9 +32,7 @@ import (
)

func TestBuildGenericConfig(t *testing.T) {
featureGate := featuregate.NewFeatureGate()
effectiveVersion := utilversion.DefaultKubeEffectiveVersion()
opts := options.NewOptions(featureGate, effectiveVersion)
opts := options.NewOptions()
s := (&apiserveroptions.SecureServingOptions{
BindAddress: netutils.ParseIPSloppy("127.0.0.1"),
}).WithLoopback()
Expand Down
6 changes: 2 additions & 4 deletions pkg/controlplane/apiserver/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,8 @@ import (
peerreconcilers "k8s.io/apiserver/pkg/reconcilers"
genericoptions "k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/storage/storagebackend"
utilversion "k8s.io/apiserver/pkg/util/version"
"k8s.io/client-go/util/keyutil"
cliflag "k8s.io/component-base/cli/flag"
"k8s.io/component-base/featuregate"
"k8s.io/component-base/logs"
logsapi "k8s.io/component-base/logs/api/v1"
"k8s.io/component-base/metrics"
Expand Down Expand Up @@ -100,9 +98,9 @@ type CompletedOptions struct {
}

// NewOptions creates a new ServerRunOptions object with default parameters
func NewOptions(featureGate featuregate.FeatureGate, effectiveVersion utilversion.EffectiveVersion) *Options {
func NewOptions() *Options {
s := Options{
GenericServerRunOptions: genericoptions.NewServerRunOptions(featureGate, effectiveVersion),
GenericServerRunOptions: genericoptions.NewServerRunOptions(),
Etcd: genericoptions.NewEtcdOptions(storagebackend.NewDefaultConfig(kubeoptions.DefaultEtcdPathPrefix, nil)),
SecureServing: kubeoptions.NewSecureServingOptions(),
Audit: genericoptions.NewAuditOptions(),
Expand Down
23 changes: 12 additions & 11 deletions pkg/controlplane/apiserver/options/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,18 @@ import (
)

func TestAddFlags(t *testing.T) {
componentGlobalsRegistry := utilversion.DefaultComponentGlobalsRegistry
t.Cleanup(func() {
componentGlobalsRegistry.Reset()
})
fs := pflag.NewFlagSet("addflagstest", pflag.PanicOnError)
featureGate := featuregate.NewFeatureGate()
effectiveVersion := utilversion.NewEffectiveVersion("1.32")
componentRegistry := utilversion.NewComponentGlobalsRegistry()
utilruntime.Must(componentRegistry.Register("test", effectiveVersion, featureGate))
s := NewOptions(featureGate, effectiveVersion)
utilruntime.Must(componentGlobalsRegistry.Register("test", utilversion.NewEffectiveVersion("1.32"), featuregate.NewFeatureGate()))
s := NewOptions()
var fss cliflag.NamedFlagSets
s.AddFlags(&fss)
for _, f := range fss.FlagSets {
fs.AddFlagSet(f)
}
componentRegistry.AddFlags(fs)

args := []string{
"--enable-admission-plugins=AlwaysDeny",
Expand Down Expand Up @@ -119,7 +119,7 @@ func TestAddFlags(t *testing.T) {
"--emulated-version=test=1.31",
}
fs.Parse(args)
utilruntime.Must(componentRegistry.Set())
utilruntime.Must(componentGlobalsRegistry.Set())

// This is a snapshot of expected options parsed by args.
expected := &Options{
Expand All @@ -132,8 +132,8 @@ func TestAddFlags(t *testing.T) {
MinRequestTimeout: 1800,
JSONPatchMaxCopyBytes: int64(3 * 1024 * 1024),
MaxRequestBodyBytes: int64(3 * 1024 * 1024),
FeatureGate: featureGate,
EffectiveVersion: effectiveVersion,
ComponentGlobalsRegistry: componentGlobalsRegistry,
ComponentName: utilversion.DefaultKubeComponent,
},
Admission: &kubeoptions.AdmissionOptions{
GenericAdmission: &apiserveroptions.AdmissionOptions{
Expand Down Expand Up @@ -305,7 +305,8 @@ func TestAddFlags(t *testing.T) {
t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", cmp.Diff(expected, s, cmpopts.IgnoreUnexported(admission.Plugins{}, kubeoptions.OIDCAuthenticationOptions{})))
}

if s.GenericServerRunOptions.EffectiveVersion.EmulationVersion().String() != "1.31" {
t.Errorf("Got emulation version %s, wanted %s", s.GenericServerRunOptions.EffectiveVersion.EmulationVersion().String(), "1.31")
testEffectiveVersion := s.GenericServerRunOptions.ComponentGlobalsRegistry.EffectiveVersionFor("test")
if testEffectiveVersion.EmulationVersion().String() != "1.31" {
t.Errorf("Got emulation version %s, wanted %s", testEffectiveVersion.EmulationVersion().String(), "1.31")
}
}
4 changes: 2 additions & 2 deletions pkg/controlplane/apiserver/options/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func TestValidateOptions(t *testing.T) {
name: "validate master count equal 0",
expectErrors: true,
options: &Options{
GenericServerRunOptions: &genericoptions.ServerRunOptions{FeatureGate: utilfeature.DefaultFeatureGate.DeepCopy(), EffectiveVersion: utilversion.NewEffectiveVersion("1.32")},
GenericServerRunOptions: &genericoptions.ServerRunOptions{ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry},
Etcd: &genericoptions.EtcdOptions{},
SecureServing: &genericoptions.SecureServingOptionsWithLoopback{},
Audit: &genericoptions.AuditOptions{},
Expand All @@ -228,7 +228,7 @@ func TestValidateOptions(t *testing.T) {
name: "validate token request enable not attempted",
expectErrors: true,
options: &Options{
GenericServerRunOptions: &genericoptions.ServerRunOptions{FeatureGate: utilfeature.DefaultFeatureGate.DeepCopy(), EffectiveVersion: utilversion.NewEffectiveVersion("1.32")},
GenericServerRunOptions: &genericoptions.ServerRunOptions{ComponentGlobalsRegistry: utilversion.DefaultComponentGlobalsRegistry},
Etcd: &genericoptions.EtcdOptions{},
SecureServing: &genericoptions.SecureServingOptionsWithLoopback{},
Audit: &genericoptions.AuditOptions{},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,9 @@ import (
flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request"
"k8s.io/apiserver/pkg/util/openapi"
"k8s.io/apiserver/pkg/util/proxy"
utilversion "k8s.io/apiserver/pkg/util/version"
"k8s.io/apiserver/pkg/util/webhook"
scheme "k8s.io/client-go/kubernetes/scheme"
corev1 "k8s.io/client-go/listers/core/v1"
"k8s.io/component-base/featuregate"
netutils "k8s.io/utils/net"
)

Expand All @@ -60,9 +58,9 @@ type CustomResourceDefinitionsServerOptions struct {
}

// NewCustomResourceDefinitionsServerOptions creates default options of an apiextensions-apiserver.
func NewCustomResourceDefinitionsServerOptions(out, errOut io.Writer, featureGate featuregate.FeatureGate, effectiveVersion utilversion.EffectiveVersion) *CustomResourceDefinitionsServerOptions {
func NewCustomResourceDefinitionsServerOptions(out, errOut io.Writer) *CustomResourceDefinitionsServerOptions {
o := &CustomResourceDefinitionsServerOptions{
ServerRunOptions: genericoptions.NewServerRunOptions(featureGate, effectiveVersion),
ServerRunOptions: genericoptions.NewServerRunOptions(),
RecommendedOptions: genericoptions.NewRecommendedOptions(
defaultEtcdPathPrefix,
apiserver.Codecs.LegacyCodec(v1beta1.SchemeGroupVersion, v1.SchemeGroupVersion),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,11 @@ import (

"k8s.io/apiextensions-apiserver/pkg/cmd/server/options"
genericapiserver "k8s.io/apiserver/pkg/server"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilversion "k8s.io/apiserver/pkg/util/version"
)

func NewServerCommand(ctx context.Context, out, errOut io.Writer) *cobra.Command {
// effectiveVersion is used to set what apis and feature gates the generic api server is compatible with.
// You can also have the flag setting the effectiveVersion of the apiextensions apiserver, and
// having a mapping from the apiextensions apiserver version to generic apiserver version.
effectiveVersion, featureGate := utilversion.DefaultComponentGlobalsRegistry.ComponentGlobalsOrRegister(
utilversion.DefaultKubeComponent, utilversion.DefaultKubeEffectiveVersion(), utilfeature.DefaultMutableFeatureGate)
o := options.NewCustomResourceDefinitionsServerOptions(out, errOut, featureGate, effectiveVersion)
o := options.NewCustomResourceDefinitionsServerOptions(out, errOut)

cmd := &cobra.Command{
Short: "Launch an API extensions API server",
Expand All @@ -58,7 +52,6 @@ func NewServerCommand(ctx context.Context, out, errOut io.Writer) *cobra.Command
cmd.SetContext(ctx)

fs := cmd.Flags()
utilversion.DefaultComponentGlobalsRegistry.AddFlags(fs)
o.AddFlags(fs)
return cmd
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,7 @@ func StartTestServer(t Logger, _ *TestServerInstanceOptions, customFlags []strin
effectiveVersion := utilversion.DefaultKubeEffectiveVersion()
utilversion.DefaultComponentGlobalsRegistry.Reset()
utilruntime.Must(utilversion.DefaultComponentGlobalsRegistry.Register(utilversion.DefaultKubeComponent, effectiveVersion, featureGate))
s := options.NewCustomResourceDefinitionsServerOptions(os.Stdout, os.Stderr, featureGate, effectiveVersion)

utilversion.DefaultComponentGlobalsRegistry.AddFlags(fs)
s := options.NewCustomResourceDefinitionsServerOptions(os.Stdout, os.Stderr)
s.AddFlags(fs)

s.RecommendedOptions.SecureServing.Listener, s.RecommendedOptions.SecureServing.BindPort, err = createLocalhostListenerOnFreePort()
Expand Down
8 changes: 4 additions & 4 deletions staging/src/k8s.io/apiserver/pkg/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -832,7 +832,7 @@ func (c completedConfig) New(name string, delegationTarget DelegationTarget) (*G
muxAndDiscoveryCompleteSignals: map[string]<-chan struct{}{},
}

if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AggregatedDiscoveryEndpoint) {
if c.FeatureGate.Enabled(genericfeatures.AggregatedDiscoveryEndpoint) {
manager := c.AggregatedDiscoveryGroupManager
if manager == nil {
manager = discoveryendpoint.NewResourceManager("apis")
Expand Down Expand Up @@ -1047,14 +1047,14 @@ func DefaultBuildHandlerChain(apiHandler http.Handler, c *Config) http.Handler {
handler = genericfilters.WithRetryAfter(handler, c.lifecycleSignals.NotAcceptingNewRequest.Signaled())
}
handler = genericfilters.WithHTTPLogging(handler)
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) {
if c.FeatureGate.Enabled(genericfeatures.APIServerTracing) {
handler = genericapifilters.WithTracing(handler, c.TracerProvider)
}
handler = genericapifilters.WithLatencyTrackers(handler)
// WithRoutine will execute future handlers in a separate goroutine and serving
// handler in current goroutine to minimize the stack memory usage. It must be
// after WithPanicRecover() to be protected from panics.
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServingWithRoutine) {
if c.FeatureGate.Enabled(genericfeatures.APIServingWithRoutine) {
handler = genericfilters.WithRoutine(handler, c.LongRunningFunc)
}
handler = genericapifilters.WithRequestInfo(handler, c.RequestInfoResolver)
Expand Down Expand Up @@ -1098,7 +1098,7 @@ func installAPI(s *GenericAPIServer, c *Config) {
routes.Version{Version: c.EffectiveVersion.BinaryVersion().Info()}.Install(s.Handler.GoRestfulContainer)

if c.EnableDiscovery {
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AggregatedDiscoveryEndpoint) {
if c.FeatureGate.Enabled(genericfeatures.AggregatedDiscoveryEndpoint) {
wrapped := discoveryendpoint.WrapAggregatedDiscoveryToHandler(s.DiscoveryGroupManager, s.AggregatedDiscoveryGroupManager)
s.Handler.GoRestfulContainer.Add(wrapped.GenerateWebService("/apis", metav1.APIGroupList{}))
} else {
Expand Down
2 changes: 2 additions & 0 deletions staging/src/k8s.io/apiserver/pkg/server/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/server/healthz"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilversion "k8s.io/apiserver/pkg/util/version"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
Expand Down Expand Up @@ -308,6 +309,7 @@ func TestAuthenticationAuditAnnotationsDefaultChain(t *testing.T) {
LongRunningFunc: func(_ *http.Request, _ *request.RequestInfo) bool { return false },
lifecycleSignals: newLifecycleSignals(),
TracerProvider: tracing.NewNoopTracerProvider(),
FeatureGate: utilfeature.DefaultFeatureGate,
}

h := DefaultBuildHandlerChain(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
Loading

0 comments on commit 379676c

Please sign in to comment.