diff --git a/cmd/kubectl-directpv/install.go b/cmd/kubectl-directpv/install.go
index 0c482d491..78ec556bc 100644
--- a/cmd/kubectl-directpv/install.go
+++ b/cmd/kubectl-directpv/install.go
@@ -20,6 +20,7 @@ import (
"context"
"fmt"
"os"
+ "path"
"github.com/fatih/color"
"github.com/minio/directpv/pkg/consts"
@@ -48,6 +49,7 @@ var (
apparmorProfile = ""
auditInstall = "install"
imagePullSecrets = []string{}
+ configFile = path.Join(os.Getenv("HOME"), consts.ConfigFileSuffix)
)
func init() {
@@ -62,6 +64,7 @@ func init() {
installCmd.PersistentFlags().StringSliceVarP(&tolerationParameters, "tolerations", "t", tolerationParameters, "Tolerations parameters")
installCmd.PersistentFlags().StringVarP(&seccompProfile, "seccomp-profile", "", seccompProfile, "Set Seccomp profile")
installCmd.PersistentFlags().StringVarP(&apparmorProfile, "apparmor-profile", "", apparmorProfile, "Set Apparmor profile")
+ installCmd.PersistentFlags().StringVarP(&configFile, fmt.Sprintf("%s-config", consts.AppName), "", configFile, fmt.Sprintf("Specify %s config file path", consts.AppPrettyName))
}
func install(ctx context.Context, args []string) (err error) {
@@ -100,6 +103,7 @@ func install(ctx context.Context, args []string) (err error) {
DryRun: dryRun,
AuditFile: file,
ImagePullSecrets: imagePullSecrets,
+ ConfigFile: configFile,
}
if err = installer.Install(ctx, installConfig); err == nil && !dryRun {
diff --git a/go.mod b/go.mod
index 76827d14e..09a103640 100644
--- a/go.mod
+++ b/go.mod
@@ -4,22 +4,20 @@ go 1.17
require (
github.com/container-storage-interface/spec v1.3.0
- github.com/docker/distribution v2.8.1+incompatible
github.com/dustin/go-humanize v1.0.0
github.com/fatih/color v1.12.0
- github.com/google/uuid v1.1.2
+ github.com/google/uuid v1.3.0
+ github.com/hashicorp/errwrap v1.1.0
github.com/jedib0t/go-pretty v4.3.0+incompatible
- github.com/jedib0t/go-pretty/v6 v6.0.5
+ github.com/jedib0t/go-pretty/v6 v6.3.9
github.com/kubernetes-csi/csi-lib-utils v0.7.0
- github.com/mb0/glob v0.0.0-20160210091149-1eb79d2de6c4
+ github.com/minio/minio-go/v7 v7.0.36
github.com/minio/sha256-simd v1.0.0
github.com/mitchellh/go-homedir v1.1.0
- github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
github.com/prometheus/client_golang v1.12.2
github.com/prometheus/client_model v0.2.0
github.com/spf13/cobra v1.4.0
github.com/spf13/viper v1.8.1
- go.uber.org/multierr v1.6.0
golang.org/x/sys v0.0.0-20220913175220-63ea55921009
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
google.golang.org/grpc v1.40.0
@@ -28,7 +26,6 @@ require (
k8s.io/apiextensions-apiserver v0.24.3
k8s.io/apimachinery v0.24.3
k8s.io/client-go v0.24.3
- k8s.io/klog v1.0.0
k8s.io/klog/v2 v2.60.1
k8s.io/kube-openapi v0.0.0-20220627174259-011e075b9cb8
sigs.k8s.io/yaml v1.2.0
@@ -65,19 +62,17 @@ require (
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/gofuzz v1.2.0 // indirect
- github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f // indirect
- github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.7 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/klauspost/cpuid/v2 v2.0.4 // indirect
+ github.com/klauspost/cpuid/v2 v2.1.0 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
- github.com/mattn/go-runewidth v0.0.9 // indirect
+ github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -88,30 +83,26 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/common v0.32.1 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
- github.com/smartystreets/assertions v1.1.1 // indirect
+ github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/afero v1.6.0 // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
- github.com/yuin/goldmark v1.4.14 // indirect
go.mongodb.org/mongo-driver v1.9.1 // indirect
- go.uber.org/atomic v1.7.0 // indirect
- golang.org/x/crypto v0.0.0-20220214200702-86341886e292 // indirect
- golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
+ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
golang.org/x/text v0.3.7 // indirect
- golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
- gopkg.in/ini.v1 v1.62.0 // indirect
+ gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- gopkg.in/yaml.v3 v3.0.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect
diff --git a/go.sum b/go.sum
index c64c8f204..d5e592136 100644
--- a/go.sum
+++ b/go.sum
@@ -291,16 +291,15 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
-github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f h1:4Gslotqbs16iAg+1KR/XdabIfq8TlAWHdwS5QJFksLc=
-github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
@@ -342,8 +341,8 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
-github.com/jedib0t/go-pretty/v6 v6.0.5 h1:oOo0/jSb3NEYKT6l1hhFXoX2UZnkanMuCE2DVT1mqnE=
-github.com/jedib0t/go-pretty/v6 v6.0.5/go.mod h1:MTr6FgcfNdnN5wPVBzJ6mhJeDyiF0yBvS2TMXEV/XSU=
+github.com/jedib0t/go-pretty/v6 v6.3.9 h1:GAK/1WJY9WVVrKd601HGB89ihLBDfJnUIJye31PY+uk=
+github.com/jedib0t/go-pretty/v6 v6.3.9/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -358,7 +357,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
@@ -367,8 +365,11 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
-github.com/klauspost/cpuid/v2 v2.0.4 h1:g0I61F2K2DjRHz1cnxlkNSBIaePVoJIjjnHui8QHbiw=
+github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
+github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
+github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
@@ -396,12 +397,15 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
-github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
+github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
+github.com/minio/minio-go/v7 v7.0.36 h1:KPzAl8C6jcRFEUsGUHR6deRivvKATPNZThzi7D9y/sc=
+github.com/minio/minio-go/v7 v7.0.36/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
@@ -466,7 +470,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
+github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -502,9 +506,12 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -515,10 +522,8 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
-github.com/smartystreets/assertions v1.1.1 h1:T/YLemO5Yp7KPzS+lVtu+WsHn8yoSwTfItdAd1r3cck=
-github.com/smartystreets/assertions v1.1.1/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
-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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
@@ -547,14 +552,17 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
+github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
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/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
@@ -572,10 +580,6 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark v1.4.14 h1:jwww1XQfhJN7Zm+/a1ZA/3WUiEBEroYFNTiV3dKwM8U=
-github.com/yuin/goldmark v1.4.14/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
@@ -630,8 +634,9 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220214200702-86341886e292 h1:f+lwQ+GtmgoY+A2YaQxlSOnDjXcQ7ZRLWOHbC6HtRqE=
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
+golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -668,8 +673,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -721,9 +724,7 @@ golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591 h1:D0B/7al0LLrVC8aWF4+oxpv/m8bc7ViFfVS8/gXGdqI=
golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
@@ -754,7 +755,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -824,10 +824,12 @@ golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 h1:OH54vjqzRWmbJ62fjuhxy7AxFFgoHN0/DPc/UrL8cAs=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220913175220-63ea55921009 h1:PuvuRMeLWqsf/ZdT1UUZz0syhioyv1mzuFZsXs4fvhw=
golang.org/x/sys v0.0.0-20220913175220-63ea55921009/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
@@ -913,8 +915,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
-golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -1049,8 +1049,9 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
+gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
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/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
@@ -1068,8 +1069,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go
index bdc77e931..d5ff548d0 100644
--- a/pkg/consts/consts.go
+++ b/pkg/consts/consts.go
@@ -130,4 +130,12 @@ const (
PrivateKeyFileName = "key.pem"
PublicCertFileName = "cert.pem"
CACertFileName = "ca.crt"
+
+ // Credentials
+ CredentialsSecretName = AppName + "-creds"
+ AccessKeyDataKey = "accessKey"
+ SecretKeyDataKey = "secretKey"
+ ConfigFileSuffix = "." + AppName + "/config.json"
+ AccessKeyEnv = AppCapsName + "_ACCESS_KEY"
+ SecretKeyEnv = AppCapsName + "_SECRET_KEY"
)
diff --git a/pkg/consts/consts.go.in b/pkg/consts/consts.go.in
index cb04d329c..672ab4f57 100644
--- a/pkg/consts/consts.go.in
+++ b/pkg/consts/consts.go.in
@@ -128,4 +128,12 @@ const (
PrivateKeyFileName = "key.pem"
PublicCertFileName = "cert.pem"
CACertFileName = "ca.crt"
+
+ // Credentials
+ CredentialsSecretName = AppName + "-creds"
+ AccessKeyDataKey = "accessKey"
+ SecretKeyDataKey = "secretKey"
+ ConfigFileSuffix = "." + AppName + "/config.json"
+ AccessKeyEnv = AppCapsName + "_ACCESS_KEY"
+ SecretKeyEnv = AppCapsName + "_SECRET_KEY"
)
diff --git a/pkg/credential/credential.go b/pkg/credential/credential.go
new file mode 100644
index 000000000..e8178005c
--- /dev/null
+++ b/pkg/credential/credential.go
@@ -0,0 +1,120 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2021, 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package credential
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "io/ioutil"
+ "os"
+
+ "github.com/minio/directpv/pkg/consts"
+ "github.com/minio/directpv/pkg/k8s"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "k8s.io/klog/v2"
+)
+
+var (
+ errSecretKeyEnvNotSet = errors.New(consts.SecretKeyEnv + " environment variable is not set")
+ errAccessKeyEnvNotSet = errors.New(consts.AccessKeyEnv + " environment variable is not set")
+ errEnvNotFound = errors.New("no environment variable settings for credentials found")
+ errSecretNotFound = errors.New("credential secret " + consts.CredentialsSecretName + " not found")
+ errAccessKeyNotFound = errors.New("accessKey not found in the secret")
+ errSecretKeyNotFound = errors.New("secretKey not found in the secret")
+)
+
+// Credential represents the access and secret key pairs for authentication
+type Credential struct {
+ AccessKey string `json:"accessKey"`
+ SecretKey string `json:"secretKey"`
+}
+
+// Load loads the credential from the ENV. If ENV settings are not present, it loads from the config file provided
+func Load(configFile string) (Credential, error) {
+ cred, err := loadFromEnv()
+ if err != nil {
+ if err != errEnvNotFound {
+ return cred, err
+ }
+ } else {
+ return cred, err
+ }
+ return loadFromFile(configFile)
+}
+
+// LoadFromSecret loads the credential from the k8s secret
+func LoadFromSecret(ctx context.Context) (cred Credential, err error) {
+ credSecret, err := k8s.KubeClient().CoreV1().Secrets(consts.Namespace).Get(context.Background(), consts.CredentialsSecretName, metav1.GetOptions{})
+ if err != nil {
+ if apierrors.IsNotFound(err) {
+ err = errSecretNotFound
+ return
+ }
+ return cred, err
+ }
+
+ accessKey, ok := credSecret.Data[consts.AccessKeyDataKey]
+ if !ok {
+ return cred, errAccessKeyNotFound
+ }
+ cred.AccessKey = string(accessKey)
+
+ secretKey, ok := credSecret.Data[consts.SecretKeyDataKey]
+ if !ok {
+ return cred, errSecretKeyNotFound
+ }
+ cred.SecretKey = string(secretKey)
+
+ return cred, nil
+}
+
+// loadFromEnv loads the credential from ENVs
+func loadFromEnv() (cred Credential, err error) {
+ accessKey, accessKeySet := os.LookupEnv(consts.AccessKeyEnv)
+ secretKey, secretKeySet := os.LookupEnv(consts.SecretKeyEnv)
+
+ switch {
+ case accessKeySet && secretKeySet:
+ cred.AccessKey = accessKey
+ cred.SecretKey = secretKey
+ case accessKeySet && !secretKeySet:
+ err = errSecretKeyEnvNotSet
+ case secretKeySet && !accessKeySet:
+ err = errAccessKeyEnvNotSet
+ case !accessKeySet && !secretKeySet:
+ err = errEnvNotFound
+ }
+
+ return
+}
+
+// loadFromFile loads the credential from provided config file
+func loadFromFile(configFile string) (Credential, error) {
+ cred := Credential{}
+ fileData, err := ioutil.ReadFile(configFile)
+ if err != nil {
+ return cred, err
+ }
+ err = json.Unmarshal(fileData, &cred)
+ if err != nil {
+ klog.Infof("\n unable to parse the config file %s: %v", configFile, err)
+ }
+ return cred, err
+}
diff --git a/pkg/device/utils.go b/pkg/device/utils.go
index 7b21275a6..c9694782a 100644
--- a/pkg/device/utils.go
+++ b/pkg/device/utils.go
@@ -167,19 +167,19 @@ func updateFSInfo(device *Device) error {
udevFSType := device.FSType()
if udevFSType == "" || strings.EqualFold(udevFSType, "xfs") {
fsuuid, _, totalCapacity, freeCapacity, err := xfs.Probe(device.Path())
- if err != nil && device.Size > 0 {
- switch {
- case errors.Is(err, xfs.ErrFSNotFound), errors.Is(err, xfs.ErrCanceled), errors.Is(err, io.ErrUnexpectedEOF):
- default:
- return err
- }
- }
if err != nil {
- return err
+ if device.Size > 0 {
+ switch {
+ case errors.Is(err, xfs.ErrFSNotFound), errors.Is(err, xfs.ErrCanceled), errors.Is(err, io.ErrUnexpectedEOF):
+ default:
+ return err
+ }
+ }
+ } else {
+ device.FSUUID = fsuuid
+ device.TotalCapacity = totalCapacity
+ device.FreeCapacity = freeCapacity
}
- device.FSUUID = fsuuid
- device.TotalCapacity = totalCapacity
- device.FreeCapacity = freeCapacity
}
return nil
}
diff --git a/pkg/installer/config.go b/pkg/installer/config.go
index 2a7e289c1..2255fb956 100644
--- a/pkg/installer/config.go
+++ b/pkg/installer/config.go
@@ -80,6 +80,9 @@ type Config struct {
// Image pull secrets
ImagePullSecrets []string
+
+ // Credential
+ ConfigFile string
}
type installer interface {
diff --git a/pkg/installer/default.go b/pkg/installer/default.go
index 87d1b47e4..5486abb58 100644
--- a/pkg/installer/default.go
+++ b/pkg/installer/default.go
@@ -50,6 +50,20 @@ func (v *defaultInstaller) installNS(ctx context.Context) error {
return err
}
+func (v *defaultInstaller) installSecrets(ctx context.Context) error {
+ timer := time.AfterFunc(
+ 3*time.Second,
+ func() { fmt.Fprintln(os.Stderr, color.HiYellowString("WARNING: too long to create Secrets")) },
+ )
+ defer timer.Stop()
+
+ err := installSecretsDefault(ctx, v.Config)
+ if err != nil && !v.DryRun {
+ fmt.Fprintf(os.Stderr, "%v unable to create Secrets; %v", color.HiRedString("ERROR"), err)
+ }
+ return err
+}
+
func (v *defaultInstaller) installRBAC(ctx context.Context) error {
timer := time.AfterFunc(
3*time.Second,
@@ -195,6 +209,20 @@ func (v *defaultInstaller) uninstallNS(ctx context.Context) error {
return err
}
+func (v *defaultInstaller) uninstallSecrets(ctx context.Context) error {
+ timer := time.AfterFunc(
+ 3*time.Second,
+ func() { fmt.Fprintln(os.Stderr, color.HiYellowString("WARNING: too long to delete Secrets")) },
+ )
+ defer timer.Stop()
+
+ err := uninstallSecretsDefault(ctx, v.Config)
+ if err != nil && !v.DryRun {
+ fmt.Fprintf(os.Stderr, "%v unable to delete Secrets; %v", color.HiRedString("ERROR"), err)
+ }
+ return err
+}
+
func (v *defaultInstaller) uninstallRBAC(ctx context.Context) error {
timer := time.AfterFunc(
3*time.Second,
@@ -329,6 +357,9 @@ func (v *defaultInstaller) Install(ctx context.Context) error {
if err := v.installNS(ctx); err != nil {
return err
}
+ if err := v.installSecrets(ctx); err != nil {
+ return err
+ }
if err := v.installRBAC(ctx); err != nil {
return err
}
@@ -384,5 +415,8 @@ func (v *defaultInstaller) Uninstall(ctx context.Context) error {
if err := v.uninstallRBAC(ctx); err != nil {
return err
}
+ if err := v.uninstallSecrets(ctx); err != nil {
+ return err
+ }
return v.uninstallNS(ctx)
}
diff --git a/pkg/installer/rbac.go b/pkg/installer/rbac.go
index 009b11d80..37e6fd7a7 100644
--- a/pkg/installer/rbac.go
+++ b/pkg/installer/rbac.go
@@ -312,6 +312,20 @@ func createClusterRole(ctx context.Context, c *Config) error {
"",
},
},
+ {
+ Verbs: []string{
+ clusterRoleVerbGet,
+ clusterRoleVerbList,
+ clusterRoleVerbWatch,
+ },
+ Resources: []string{
+ "secrets",
+ "secret",
+ },
+ APIGroups: []string{
+ "",
+ },
+ },
},
AggregationRule: nil,
}
diff --git a/pkg/installer/secrets.go b/pkg/installer/secrets.go
new file mode 100644
index 000000000..cde430729
--- /dev/null
+++ b/pkg/installer/secrets.go
@@ -0,0 +1,67 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2021, 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package installer
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "os"
+
+ "github.com/minio/directpv/pkg/consts"
+ "github.com/minio/directpv/pkg/credential"
+ "github.com/minio/directpv/pkg/k8s"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func installSecretsDefault(ctx context.Context, c *Config) error {
+ if err := installCredSecret(ctx, c); err != nil {
+ return err
+ }
+ // Add more secrets here..
+ return nil
+}
+
+func uninstallSecretsDefault(ctx context.Context, c *Config) error {
+ if err := uninstallCredSecret(ctx, c); err != nil {
+ return err
+ }
+ // Delete more secrets here
+ return nil
+}
+
+func installCredSecret(ctx context.Context, c *Config) error {
+ cred, err := credential.Load(c.ConfigFile)
+ if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ err = fmt.Errorf("credentials not found: please configure access and secret keys via ENV or config file (%s)", c.ConfigFile)
+ }
+ return err
+ }
+ return createOrUpdateSecret(ctx, consts.CredentialsSecretName, map[string][]byte{
+ consts.AccessKeyDataKey: []byte(cred.AccessKey),
+ consts.SecretKeyDataKey: []byte(cred.SecretKey),
+ }, c)
+}
+
+func uninstallCredSecret(ctx context.Context, c *Config) error {
+ if err := k8s.KubeClient().CoreV1().Secrets(c.namespace()).Delete(ctx, consts.CredentialsSecretName, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
+ return err
+ }
+ return nil
+}
diff --git a/pkg/installer/v1dot18.go b/pkg/installer/v1dot18.go
index 96192639f..0e26f8feb 100644
--- a/pkg/installer/v1dot18.go
+++ b/pkg/installer/v1dot18.go
@@ -33,6 +33,10 @@ func (v *v1dot18) installNS(ctx context.Context) error {
return installNSDefault(ctx, v.Config)
}
+func (v *v1dot18) installSecrets(ctx context.Context) error {
+ return installSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot18) installRBAC(ctx context.Context) error {
return installRBACDefault(ctx, v.Config)
}
@@ -74,6 +78,10 @@ func (v *v1dot18) uninstallNS(ctx context.Context) error {
return uninstallNSDefault(ctx, v.Config)
}
+func (v *v1dot18) uninstallSecrets(ctx context.Context) error {
+ return uninstallSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot18) uninstallRBAC(ctx context.Context) error {
return uninstallRBACDefault(ctx, v.Config)
}
@@ -114,6 +122,9 @@ func (v *v1dot18) Install(ctx context.Context) error {
if err := v.installNS(ctx); err != nil {
return err
}
+ if err := v.installSecrets(ctx); err != nil {
+ return err
+ }
if err := v.installRBAC(ctx); err != nil {
return err
}
@@ -169,5 +180,8 @@ func (v *v1dot18) Uninstall(ctx context.Context) error {
if err := v.uninstallRBAC(ctx); err != nil {
return err
}
+ if err := v.uninstallSecrets(ctx); err != nil {
+ return err
+ }
return v.uninstallNS(ctx)
}
diff --git a/pkg/installer/v1dot19.go b/pkg/installer/v1dot19.go
index 74c69706a..608f0a50b 100644
--- a/pkg/installer/v1dot19.go
+++ b/pkg/installer/v1dot19.go
@@ -33,6 +33,10 @@ func (v *v1dot19) installNS(ctx context.Context) error {
return installNSDefault(ctx, v.Config)
}
+func (v *v1dot19) installSecrets(ctx context.Context) error {
+ return installSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot19) installRBAC(ctx context.Context) error {
return installRBACDefault(ctx, v.Config)
}
@@ -74,6 +78,10 @@ func (v *v1dot19) uninstallNS(ctx context.Context) error {
return uninstallNSDefault(ctx, v.Config)
}
+func (v *v1dot19) uninstallSecrets(ctx context.Context) error {
+ return uninstallSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot19) uninstallRBAC(ctx context.Context) error {
return uninstallRBACDefault(ctx, v.Config)
}
@@ -114,6 +122,9 @@ func (v *v1dot19) Install(ctx context.Context) error {
if err := v.installNS(ctx); err != nil {
return err
}
+ if err := v.installSecrets(ctx); err != nil {
+ return err
+ }
if err := v.installRBAC(ctx); err != nil {
return err
}
@@ -169,5 +180,8 @@ func (v *v1dot19) Uninstall(ctx context.Context) error {
if err := v.uninstallRBAC(ctx); err != nil {
return err
}
+ if err := v.uninstallSecrets(ctx); err != nil {
+ return err
+ }
return v.uninstallNS(ctx)
}
diff --git a/pkg/installer/v1dot20.go b/pkg/installer/v1dot20.go
index 9e641c8ee..100dda783 100644
--- a/pkg/installer/v1dot20.go
+++ b/pkg/installer/v1dot20.go
@@ -33,6 +33,10 @@ func (v *v1dot20) installNS(ctx context.Context) error {
return installNSDefault(ctx, v.Config)
}
+func (v *v1dot20) installSecrets(ctx context.Context) error {
+ return installSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot20) installRBAC(ctx context.Context) error {
return installRBACDefault(ctx, v.Config)
}
@@ -74,6 +78,10 @@ func (v *v1dot20) uninstallNS(ctx context.Context) error {
return uninstallNSDefault(ctx, v.Config)
}
+func (v *v1dot20) uninstallSecrets(ctx context.Context) error {
+ return uninstallSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot20) uninstallRBAC(ctx context.Context) error {
return uninstallRBACDefault(ctx, v.Config)
}
@@ -114,6 +122,9 @@ func (v *v1dot20) Install(ctx context.Context) error {
if err := v.installNS(ctx); err != nil {
return err
}
+ if err := v.installSecrets(ctx); err != nil {
+ return err
+ }
if err := v.installRBAC(ctx); err != nil {
return err
}
@@ -169,5 +180,8 @@ func (v *v1dot20) Uninstall(ctx context.Context) error {
if err := v.uninstallRBAC(ctx); err != nil {
return err
}
+ if err := v.uninstallSecrets(ctx); err != nil {
+ return err
+ }
return v.uninstallNS(ctx)
}
diff --git a/pkg/installer/v1dot21.go b/pkg/installer/v1dot21.go
index 533101afe..ac1d51923 100644
--- a/pkg/installer/v1dot21.go
+++ b/pkg/installer/v1dot21.go
@@ -33,6 +33,10 @@ func (v *v1dot21) installNS(ctx context.Context) error {
return installNSDefault(ctx, v.Config)
}
+func (v *v1dot21) installSecrets(ctx context.Context) error {
+ return installSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot21) installRBAC(ctx context.Context) error {
return installRBACDefault(ctx, v.Config)
}
@@ -74,6 +78,10 @@ func (v *v1dot21) uninstallNS(ctx context.Context) error {
return uninstallNSDefault(ctx, v.Config)
}
+func (v *v1dot21) uninstallSecrets(ctx context.Context) error {
+ return uninstallSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot21) uninstallRBAC(ctx context.Context) error {
return uninstallRBACDefault(ctx, v.Config)
}
@@ -114,6 +122,9 @@ func (v *v1dot21) Install(ctx context.Context) error {
if err := v.installNS(ctx); err != nil {
return err
}
+ if err := v.installSecrets(ctx); err != nil {
+ return err
+ }
if err := v.installRBAC(ctx); err != nil {
return err
}
@@ -169,5 +180,8 @@ func (v *v1dot21) Uninstall(ctx context.Context) error {
if err := v.uninstallRBAC(ctx); err != nil {
return err
}
+ if err := v.uninstallSecrets(ctx); err != nil {
+ return err
+ }
return v.uninstallNS(ctx)
}
diff --git a/pkg/installer/v1dot22.go b/pkg/installer/v1dot22.go
index 962318ec9..9065710bf 100644
--- a/pkg/installer/v1dot22.go
+++ b/pkg/installer/v1dot22.go
@@ -33,6 +33,10 @@ func (v *v1dot22) installNS(ctx context.Context) error {
return installNSDefault(ctx, v.Config)
}
+func (v *v1dot22) installSecrets(ctx context.Context) error {
+ return installSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot22) installRBAC(ctx context.Context) error {
return installRBACDefault(ctx, v.Config)
}
@@ -74,6 +78,10 @@ func (v *v1dot22) uninstallNS(ctx context.Context) error {
return uninstallNSDefault(ctx, v.Config)
}
+func (v *v1dot22) uninstallSecrets(ctx context.Context) error {
+ return uninstallSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot22) uninstallRBAC(ctx context.Context) error {
return uninstallRBACDefault(ctx, v.Config)
}
@@ -114,6 +122,9 @@ func (v *v1dot22) Install(ctx context.Context) error {
if err := v.installNS(ctx); err != nil {
return err
}
+ if err := v.installSecrets(ctx); err != nil {
+ return err
+ }
if err := v.installRBAC(ctx); err != nil {
return err
}
@@ -169,5 +180,8 @@ func (v *v1dot22) Uninstall(ctx context.Context) error {
if err := v.uninstallRBAC(ctx); err != nil {
return err
}
+ if err := v.uninstallSecrets(ctx); err != nil {
+ return err
+ }
return v.uninstallNS(ctx)
}
diff --git a/pkg/installer/v1dot23.go b/pkg/installer/v1dot23.go
index 7aa40d650..4f47a6af0 100644
--- a/pkg/installer/v1dot23.go
+++ b/pkg/installer/v1dot23.go
@@ -33,6 +33,10 @@ func (v *v1dot23) installNS(ctx context.Context) error {
return installNSDefault(ctx, v.Config)
}
+func (v *v1dot23) installSecrets(ctx context.Context) error {
+ return installSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot23) installRBAC(ctx context.Context) error {
return installRBACDefault(ctx, v.Config)
}
@@ -74,6 +78,10 @@ func (v *v1dot23) uninstallNS(ctx context.Context) error {
return uninstallNSDefault(ctx, v.Config)
}
+func (v *v1dot23) uninstallSecrets(ctx context.Context) error {
+ return uninstallSecretsDefault(ctx, v.Config)
+}
+
func (v *v1dot23) uninstallRBAC(ctx context.Context) error {
return uninstallRBACDefault(ctx, v.Config)
}
@@ -114,6 +122,9 @@ func (v *v1dot23) Install(ctx context.Context) error {
if err := v.installNS(ctx); err != nil {
return err
}
+ if err := v.installSecrets(ctx); err != nil {
+ return err
+ }
if err := v.installRBAC(ctx); err != nil {
return err
}
@@ -169,5 +180,8 @@ func (v *v1dot23) Uninstall(ctx context.Context) error {
if err := v.uninstallRBAC(ctx); err != nil {
return err
}
+ if err := v.uninstallSecrets(ctx); err != nil {
+ return err
+ }
return v.uninstallNS(ctx)
}
diff --git a/pkg/rest/api-response.go b/pkg/rest/api-response.go
index 12e9f5512..227e9bc4d 100644
--- a/pkg/rest/api-response.go
+++ b/pkg/rest/api-response.go
@@ -40,7 +40,7 @@ func writeErrorResponse(w http.ResponseWriter, statusCode int, apiErr apiError)
var err error
responseBytes, err = json.Marshal(apiErr)
if err != nil {
- klog.Errorf("couldn't marshal the apiError %v: %v", apiErr, err)
+ klog.Errorf("couldn't marshal the apierror %v: %v", apiErr, err)
responseBytes = []byte(apiErr.Description)
}
writeResponse(w, statusCode, responseBytes)
diff --git a/pkg/rest/api.go b/pkg/rest/api-server.go
similarity index 94%
rename from pkg/rest/api.go
rename to pkg/rest/api-server.go
index 1ca6df8ca..8c80fd7bb 100644
--- a/pkg/rest/api.go
+++ b/pkg/rest/api-server.go
@@ -65,8 +65,8 @@ func ServeAPIServer(ctx context.Context, apiPort int) error {
// define http server and server handler
mux := http.NewServeMux()
- mux.HandleFunc(devicesListAPIPath, listDevicesHandler)
- mux.HandleFunc(devicesFormatAPIPath, formatDrivesHandler)
+ mux.HandleFunc(devicesListAPIPath, authMiddleware(listDevicesHandler))
+ mux.HandleFunc(devicesFormatAPIPath, authMiddleware(formatDevicesHandler))
mux.HandleFunc(consts.ReadinessPath, readinessHandler)
server.Handler = mux
@@ -122,19 +122,19 @@ func listDevicesHandler(w http.ResponseWriter, r *http.Request) {
// listDevices queries the nodes parallelly to get the available and unavailable devices
func listDevices(ctx context.Context, req GetDevicesRequest) (map[NodeName][]Device, error) {
var nodes []string
- var err error
- if len(req.Nodes) > 0 {
- nodes, err = ellipsis.Expand(string(req.Nodes))
+ for _, nodeSelector := range req.Nodes {
+ expanded, err := ellipsis.Expand(string(nodeSelector))
if err != nil {
- return nil, fmt.Errorf("couldn't expand the node selector %v: %v", req.Nodes, err)
+ return nil, fmt.Errorf("couldn't expand the node selector %v: %v", nodeSelector, err)
}
+ nodes = append(nodes, expanded...)
}
endpointsMap, nodeAPIPort, err := getNodeEndpoints(ctx)
if err != nil {
return nil, fmt.Errorf("couldn't get the node endpoints: %v", err)
}
httpClient := &http.Client{
- Transport: getTransport(),
+ Transport: getDefaultTransport(true),
}
reqBody, err := json.Marshal(GetDevicesRequest{
Drives: req.Drives,
@@ -190,8 +190,8 @@ func listDevices(ctx context.Context, req GetDevicesRequest) (map[NodeName][]Dev
return devices, nil
}
-// formatDrivesHandler forwards the format requests to respective nodes
-func formatDrivesHandler(w http.ResponseWriter, r *http.Request) {
+// formatDevicesHandler forwards the format requests to respective nodes
+func formatDevicesHandler(w http.ResponseWriter, r *http.Request) {
data, err := io.ReadAll(r.Body)
if err != nil {
klog.Errorf("couldn't read the request: %v", err)
@@ -229,7 +229,7 @@ func formatDrives(ctx context.Context, req FormatDevicesRequest) (map[NodeName][
return nil, fmt.Errorf("couldn't get the node endpoints: %v", err)
}
httpClient := &http.Client{
- Transport: getTransport(),
+ Transport: getDefaultTransport(true),
}
var wg sync.WaitGroup
var formatStatus = make(map[NodeName][]FormatDeviceStatus)
diff --git a/pkg/rest/auth.go b/pkg/rest/auth.go
new file mode 100644
index 000000000..7041ff258
--- /dev/null
+++ b/pkg/rest/auth.go
@@ -0,0 +1,105 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2021, 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package rest
+
+import (
+ "context"
+ "net/http"
+ "time"
+
+ "github.com/minio/directpv/pkg/credential"
+)
+
+// authMiddleware serves as a middleware handler to terminate un-authorized requests
+func authMiddleware(fn func(rw http.ResponseWriter, rq *http.Request)) func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ cred, err := credential.LoadFromSecret(context.Background())
+ if err != nil {
+ writeErrorResponse(w, http.StatusNotFound, toAPIError(err, "couldn't load the credential from secret"))
+ return
+ }
+ if err := doesSignatureMatch(r, cred); err != nil {
+ writeErrorResponse(w, http.StatusUnauthorized, toAPIError(err, "couldn't verify the signature"))
+ return
+ }
+ // successfully authenticated
+ fn(w, r)
+ }
+}
+
+// doesSignatureMatch verifies the s3v4 signature in the request by comparing the self-calculated s3v4 signature
+func doesSignatureMatch(r *http.Request, cred credential.Credential) error {
+ // Copy request.
+ req := *r
+
+ // Save authorization header.
+ v4Auth := req.Header.Get(authorizationHeader)
+
+ // Parse signature version '4' header.
+ signV4Values, err := parseSignV4(v4Auth)
+ if err != nil {
+ return err
+ }
+
+ // Extract all the signed headers along with its values.
+ extractedSignedHeaders, err := extractSignedHeaders(signV4Values.SignedHeaders, r)
+ if err != nil {
+ return err
+ }
+
+ if signV4Values.Credential.accessKey != cred.AccessKey {
+ return errWrongAccessKey
+ }
+
+ // Extract date, if not present throw error.
+ var date string
+ if date = req.Header.Get(amzDateHeaderKey); date == "" {
+ if date = r.Header.Get(dateHeaderKey); date == "" {
+ return errMissingDateHeader
+ }
+ }
+
+ // Parse date header.
+ t, e := time.Parse(iso8601Format, date)
+ if e != nil {
+ return errMalformedDate
+ }
+
+ // Query string.
+ queryStr := req.Form.Encode()
+
+ // Get canonical request.
+ canonicalRequest := getCanonicalRequest(extractedSignedHeaders, queryStr, req.URL.Path, req.Method)
+
+ // Get string to sign from canonical request.
+ stringToSign := getStringToSign(canonicalRequest, t, signV4Values.Credential.getScope())
+
+ // Get hmac signing key.
+ signingKey := getSigningKey(cred.SecretKey, signV4Values.Credential.scope.date,
+ signV4Values.Credential.scope.region, "s3")
+
+ // Calculate signature.
+ newSignature := getSignature(signingKey, stringToSign)
+
+ // Verify if signature match.
+ if !compareSignatureV4(newSignature, signV4Values.Signature) {
+ return errSignatureDoesNotMatch
+ }
+
+ // Return error none.
+ return nil
+}
diff --git a/pkg/rest/client.go b/pkg/rest/client.go
new file mode 100644
index 000000000..73f3c0646
--- /dev/null
+++ b/pkg/rest/client.go
@@ -0,0 +1,366 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2021, 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package rest
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "os"
+ "regexp"
+ "runtime"
+ "strings"
+
+ "github.com/minio/directpv/pkg/consts"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+ "github.com/minio/minio-go/v7/pkg/signer"
+)
+
+var (
+ errEmptyResponse = errors.New("response is empty")
+)
+
+const (
+ libraryVersion = "1.0"
+ // User Agent follows the below style.
+ // DirectPV (OS; ARCH) LIB/VER APP/VER
+ libraryUserAgentPrefix = consts.AppPrettyName + " (" + runtime.GOOS + "; " + runtime.GOARCH + ") "
+ libraryUserAgent = libraryUserAgentPrefix + consts.AppName + "/" + libraryVersion
+)
+
+type AdminClient struct {
+ // parsed endpoint provided by the user
+ endpointURL *url.URL
+ // Authorization keys
+ accessKey string
+ secretKey string
+ // Indicate whether we are using https or not
+ secure bool
+ // Needs http client to be initialized
+ httpClient *http.Client
+
+ // Advanced functionality
+ isTraceEnabled bool
+ traceOutput io.Writer
+ // User supplied origin info
+ appInfo struct {
+ appName string
+ appVersion string
+ }
+}
+
+// New initiates a new admin client
+func New(endpoint, accessKey, secretKey string, secure bool) (*AdminClient, error) {
+ clnt, err := privateNew(endpoint, accessKey, secretKey, secure)
+ if err != nil {
+ return nil, err
+ }
+ return clnt, nil
+}
+
+func privateNew(endpoint, accessKey, secretKey string, secure bool) (*AdminClient, error) {
+ endpointURL, err := getEndpointURL(endpoint, secure)
+ if err != nil {
+ return nil, err
+ }
+
+ clnt := new(AdminClient)
+
+ // Set the creds
+ clnt.accessKey = accessKey
+ clnt.secretKey = secretKey
+
+ // Remember whether we are using https or not
+ clnt.secure = secure
+
+ // Save endpoint URL, user agent for future uses.
+ clnt.endpointURL = endpointURL
+
+ // Instantiate http client and bucket location cache.
+ clnt.httpClient = &http.Client{
+ Transport: getDefaultTransport(secure),
+ }
+
+ return clnt, nil
+}
+
+// SetAppInfo - add application details to user agent.
+func (adm *AdminClient) SetAppInfo(appName string, appVersion string) {
+ if appName != "" && appVersion != "" {
+ adm.appInfo.appName = appName
+ adm.appInfo.appVersion = appVersion
+ }
+}
+
+// SetCustomTransport - set new custom transport.
+func (adm *AdminClient) SetCustomTransport(customHTTPTransport http.RoundTripper) {
+ // Set this to override default transport
+ // ``http.DefaultTransport``.
+ //
+ // This transport is usually needed for debugging OR to add your
+ // own custom TLS certificates on the client transport, for custom
+ // CA's and certs which are not part of standard certificate
+ // authority follow this example :-
+ //
+ // tr := &http.Transport{
+ // TLSClientConfig: &tls.Config{RootCAs: pool},
+ // DisableCompression: true,
+ // }
+ // api.SetTransport(tr)
+ //
+ if adm.httpClient != nil {
+ adm.httpClient.Transport = customHTTPTransport
+ }
+}
+
+// TraceOn - enable HTTP tracing.
+func (adm *AdminClient) TraceOn(outputStream io.Writer) {
+ // if outputStream is nil then default to os.Stdout.
+ if outputStream == nil {
+ outputStream = os.Stdout
+ }
+ // Sets a new output stream.
+ adm.traceOutput = outputStream
+
+ // Enable tracing.
+ adm.isTraceEnabled = true
+}
+
+// TraceOff - disable HTTP tracing.
+func (adm *AdminClient) TraceOff() {
+ // Disable tracing.
+ adm.isTraceEnabled = false
+}
+
+// Filter out signature value from Authorization header.
+func (adm AdminClient) filterSignature(req *http.Request) {
+ /// Signature V4 authorization header.
+
+ // Save the original auth.
+ origAuth := req.Header.Get("Authorization")
+ // Strip out accessKeyID from:
+ // Credential=////aws4_request
+ regCred := regexp.MustCompile("Credential=([A-Z0-9]+)/")
+ newAuth := regCred.ReplaceAllString(origAuth, "Credential=**REDACTED**/")
+
+ // Strip out 256-bit signature from: Signature=<256-bit signature>
+ regSign := regexp.MustCompile("Signature=([[0-9a-f]+)")
+ newAuth = regSign.ReplaceAllString(newAuth, "Signature=**REDACTED**")
+
+ // Set a temporary redacted auth
+ req.Header.Set("Authorization", newAuth)
+}
+
+// dumpHTTP - dump HTTP request and response.
+func (adm AdminClient) dumpHTTP(req *http.Request, resp *http.Response) error {
+ // Starts http dump.
+ _, err := fmt.Fprintln(adm.traceOutput, "---------START-HTTP---------")
+ if err != nil {
+ return err
+ }
+
+ // Filter out Signature field from Authorization header.
+ adm.filterSignature(req)
+
+ // Only display request header.
+ reqTrace, err := httputil.DumpRequestOut(req, false)
+ if err != nil {
+ return err
+ }
+
+ // Write request to trace output.
+ _, err = fmt.Fprint(adm.traceOutput, string(reqTrace))
+ if err != nil {
+ return err
+ }
+
+ // Only display response header.
+ var respTrace []byte
+
+ // For errors we make sure to dump response body as well.
+ if resp.StatusCode != http.StatusOK &&
+ resp.StatusCode != http.StatusPartialContent &&
+ resp.StatusCode != http.StatusNoContent {
+ respTrace, err = httputil.DumpResponse(resp, true)
+ if err != nil {
+ return err
+ }
+ } else {
+ // WORKAROUND for https://github.com/golang/go/issues/13942.
+ // httputil.DumpResponse does not print response headers for
+ // all successful calls which have response ContentLength set
+ // to zero. Keep this workaround until the above bug is fixed.
+ if resp.ContentLength == 0 {
+ var buffer bytes.Buffer
+ if err = resp.Header.Write(&buffer); err != nil {
+ return err
+ }
+ respTrace = buffer.Bytes()
+ respTrace = append(respTrace, []byte("\r\n")...)
+ } else {
+ respTrace, err = httputil.DumpResponse(resp, false)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ // Write response to trace output.
+ _, err = fmt.Fprint(adm.traceOutput, strings.TrimSuffix(string(respTrace), "\r\n"))
+ if err != nil {
+ return err
+ }
+
+ // Ends the http dump.
+ _, err = fmt.Fprintln(adm.traceOutput, "---------END-HTTP---------")
+ return err
+}
+
+// do - execute http request.
+func (adm AdminClient) do(req *http.Request) (*http.Response, error) {
+ resp, err := adm.httpClient.Do(req)
+ if err != nil {
+ // Handle this specifically for now until future Golang versions fix this issue properly.
+ if urlErr, ok := err.(*url.Error); ok {
+ if strings.Contains(urlErr.Err.Error(), "EOF") {
+ return nil, &url.Error{
+ Op: urlErr.Op,
+ URL: urlErr.URL,
+ Err: errors.New("Connection closed by foreign host " + urlErr.URL + ". Retry again."),
+ }
+ }
+ }
+ return nil, err
+ }
+
+ // Response cannot be non-nil
+ if resp == nil {
+ return nil, errEmptyResponse
+ }
+
+ // If trace is enabled, dump http request and response.
+ if adm.isTraceEnabled {
+ err = adm.dumpHTTP(req, resp)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return resp, nil
+}
+
+// set User agent.
+func (adm AdminClient) setUserAgent(req *http.Request) {
+ req.Header.Set("User-Agent", libraryUserAgent)
+ if adm.appInfo.appName != "" && adm.appInfo.appVersion != "" {
+ req.Header.Set("User-Agent", libraryUserAgent+" "+adm.appInfo.appName+"/"+adm.appInfo.appVersion)
+ }
+}
+
+// RequestData exposing internal data structure requestData
+type RequestData struct {
+ CustomHeaders http.Header
+ QueryValues url.Values
+ RelPath string // URL path relative to admin API base endpoint
+ Content []byte
+}
+
+// ExecuteMethod - similar to internal method executeMethod() useful
+// for writing custom requests.
+func (adm AdminClient) ExecuteMethod(ctx context.Context, method string, reqData RequestData) (res *http.Response, err error) {
+ return adm.executeMethod(ctx, method, reqData)
+}
+
+func (adm AdminClient) executeMethod(ctx context.Context, method string, reqData RequestData) (res *http.Response, err error) {
+ defer func() {
+ if err != nil {
+ // close idle connections before returning, upon error.
+ adm.httpClient.CloseIdleConnections()
+ }
+ }()
+
+ // Create cancel context to control 'newRetryTimer' go routine.
+ ctx, cancel := context.WithCancel(ctx)
+
+ // Indicate to our routine to exit cleanly upon return.
+ defer cancel()
+
+ // Instantiate a new request.
+ var req *http.Request
+ req, err = adm.newRequest(ctx, method, reqData)
+ if err != nil {
+ return nil, err
+ }
+
+ // Initiate the request.
+ return adm.do(req)
+}
+
+// newRequest - instantiate a new HTTP request for a given method.
+func (adm AdminClient) newRequest(ctx context.Context, method string, reqData RequestData) (req *http.Request, err error) {
+ // If no method is supplied default to 'POST'.
+ if method == "" {
+ method = "POST"
+ }
+
+ // Construct a new target URL.
+ targetURL, err := adm.makeTargetURL(reqData)
+ if err != nil {
+ return nil, err
+ }
+
+ // Initialize a new HTTP request for the method.
+ req, err = http.NewRequestWithContext(ctx, method, targetURL.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ adm.setUserAgent(req)
+ for k, v := range reqData.CustomHeaders {
+ req.Header.Set(k, v[0])
+ }
+ if length := len(reqData.Content); length > 0 {
+ req.ContentLength = int64(length)
+ }
+ req.Body = ioutil.NopCloser(bytes.NewReader(reqData.Content))
+
+ req = signer.SignV4(*req, adm.accessKey, adm.secretKey, "", "")
+ return req, nil
+}
+
+// makeTargetURL make a new target url.
+func (adm AdminClient) makeTargetURL(r RequestData) (*url.URL, error) {
+ host := adm.endpointURL.Host
+ scheme := adm.endpointURL.Scheme
+
+ // ToDo: Embed versions for a versioned API
+ urlStr := scheme + "://" + host + r.RelPath
+
+ // If there are any query values, add them to the end.
+ if len(r.QueryValues) > 0 {
+ urlStr = urlStr + "?" + s3utils.QueryEncode(r.QueryValues)
+ }
+ u, err := url.Parse(urlStr)
+ if err != nil {
+ return nil, err
+ }
+ return u, nil
+}
diff --git a/pkg/rest/devices_format.go b/pkg/rest/devices_format.go
new file mode 100644
index 000000000..9ba32a755
--- /dev/null
+++ b/pkg/rest/devices_format.go
@@ -0,0 +1,52 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2021, 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package rest
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+// FormatDevices - formats the requested devices and responds with results
+func (adm *AdminClient) FormatDevices(ctx context.Context, req FormatDevicesRequest) (result FormatDevicesResponse, err error) {
+ reqBodyInBytes, err := json.Marshal(req)
+ if err != nil {
+ return result, fmt.Errorf("errror while marshalling the request: %v", err)
+ }
+ resp, err := adm.executeMethod(ctx, "", RequestData{
+ RelPath: devicesFormatAPIPath,
+ Content: reqBodyInBytes,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return result, err
+ }
+ if resp.StatusCode != http.StatusOK {
+ return result, httpRespToErrorResponse(resp)
+ }
+ responseInBytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return result, fmt.Errorf("failed to read response: %v", err)
+ }
+ if err := json.Unmarshal(responseInBytes, &result); err != nil {
+ return result, fmt.Errorf("failed to parse the response: %v", err)
+ }
+ return
+}
diff --git a/pkg/rest/devices_list.go b/pkg/rest/devices_list.go
new file mode 100644
index 000000000..c5adb459e
--- /dev/null
+++ b/pkg/rest/devices_list.go
@@ -0,0 +1,52 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2021, 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package rest
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+// ListDevices - lists the available and unavailable devices present in the nodes. Also honours the selectors for filtering
+func (adm *AdminClient) ListDevices(ctx context.Context, req GetDevicesRequest) (result GetDevicesResponse, err error) {
+ reqBodyInBytes, err := json.Marshal(req)
+ if err != nil {
+ return result, fmt.Errorf("errror while marshalling the request: %v", err)
+ }
+ resp, err := adm.executeMethod(ctx, "", RequestData{
+ RelPath: devicesListAPIPath,
+ Content: reqBodyInBytes,
+ })
+ defer closeResponse(resp)
+ if err != nil {
+ return result, err
+ }
+ if resp.StatusCode != http.StatusOK {
+ return result, httpRespToErrorResponse(resp)
+ }
+ responseInBytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return result, fmt.Errorf("failed to read response: %v", err)
+ }
+ if err := json.Unmarshal(responseInBytes, &result); err != nil {
+ return result, fmt.Errorf("failed to parse the response: %v", err)
+ }
+ return
+}
diff --git a/pkg/rest/errors.go b/pkg/rest/errors.go
index b354e8857..ad86f81a8 100644
--- a/pkg/rest/errors.go
+++ b/pkg/rest/errors.go
@@ -16,7 +16,13 @@
package rest
-import "errors"
+import (
+ "encoding/json"
+ "errors"
+ "io"
+ "io/ioutil"
+ "net/http"
+)
var (
errNoSubsetsFound = errors.New("no subsets found for the node service")
@@ -33,9 +39,36 @@ type apiError struct {
Message string `json:"message"`
}
+func (e apiError) Error() string {
+ return e.Description + ": " + e.Message
+}
+
func toAPIError(err error, message string) apiError {
return apiError{
Description: err.Error(),
Message: message,
}
}
+
+// httpRespToErrorResponse returns a new encoded apiError structure as error.
+func httpRespToErrorResponse(resp *http.Response) error {
+ if resp == nil || resp.Body == nil {
+ return errEmptyResponse
+ }
+ defer closeResponse(resp)
+
+ // Limit to 100K
+ body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 100<<10))
+ if err != nil {
+ return toAPIError(err, "failed to read server response")
+ }
+
+ // Decode the json error
+ var errResp apiError
+ err = json.Unmarshal(body, &errResp)
+ if err != nil {
+ return toAPIError(err, "failed to parse server response")
+ }
+
+ return errResp
+}
diff --git a/pkg/rest/examples/devices_format.go b/pkg/rest/examples/devices_format.go
new file mode 100644
index 000000000..bf08e9a51
--- /dev/null
+++ b/pkg/rest/examples/devices_format.go
@@ -0,0 +1,92 @@
+//go:build ignore
+// +build ignore
+
+// This file is part of MinIO DirectPV
+// Copyright (c) 2021, 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/minio/directpv/pkg/rest"
+)
+
+func main() {
+ // Note: ACCESS_KEY, SECRET_KEY are dummy values, please replace them with original values.
+ // CAUTION: This example may format the drives. Please be careful when executing this
+ admClnt, err := rest.New(":40443", "ACCESS_KEY", "SECRET_KEY", true)
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ // Uncomment to enable trace
+ // admClnt.TraceOn(nil)
+
+ result, err := admClnt.ListDevices(context.Background(), rest.GetDevicesRequest{
+ Drives: []rest.Selector{}, // supports ellipsis. eg, `sda`, `sd{a...d}`, `sda,sd{b...d}`
+ Nodes: []rest.Selector{}, // supports ellipsis. eg, `node-1`, `node-{1...4}`, `node-1, node-{2...4}`
+ Statuses: []rest.DeviceStatus{}, // possible values are rest.DeviceStatusAvailable and rest.DeviceStatusUnavailable
+ })
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ formatInfo := make(map[rest.NodeName][]rest.FormatDevice)
+ for nodeName, deviceList := range result.DeviceInfo {
+ var devicesToFormat []rest.FormatDevice
+ for _, device := range deviceList {
+ devicesToFormat = append(devicesToFormat, rest.FormatDevice{
+ Name: device.Name,
+ MajorMinor: device.MajorMinor,
+ Force: device.Filesystem != "",
+ UDevData: device.UDevData,
+ })
+ }
+ if len(devicesToFormat) > 0 {
+ formatInfo[rest.NodeName(nodeName)] = devicesToFormat
+ }
+ }
+
+ if len(formatInfo) == 0 {
+ log.Fatal("no devices listed for formatting")
+ }
+
+ formatResult, err := admClnt.FormatDevices(context.Background(), rest.FormatDevicesRequest{
+ FormatInfo: formatInfo,
+ })
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ for nodeName, formatDeviceStatusList := range formatResult.DeviceInfo {
+ for _, formatDeviceStatus := range formatDeviceStatusList {
+ if formatDeviceStatus.Error != "" {
+ fmt.Printf("\n failed to format device: %s from node: %s due to %s. Error: %s, Suggestion: %s",
+ formatDeviceStatus.Name,
+ nodeName,
+ formatDeviceStatus.Message,
+ formatDeviceStatus.Error,
+ formatDeviceStatus.Suggestion,
+ )
+ } else {
+ fmt.Printf("\n successfully formatted device: %s from node: %s", formatDeviceStatus.Name, nodeName)
+ }
+ }
+ }
+}
diff --git a/pkg/rest/examples/devices_list.go b/pkg/rest/examples/devices_list.go
new file mode 100644
index 000000000..7ebd73278
--- /dev/null
+++ b/pkg/rest/examples/devices_list.go
@@ -0,0 +1,63 @@
+//go:build ignore
+// +build ignore
+
+// This file is part of MinIO DirectPV
+// Copyright (c) 2021, 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "log"
+
+ "github.com/dustin/go-humanize"
+ "github.com/minio/directpv/pkg/rest"
+)
+
+func main() {
+ // Note: ACCESS_KEY, SECRET_KEY are dummy values, please replace them with original values.
+ admClnt, err := rest.New(":40443", "ACCESS_KEY", "SECRET_KEY", true)
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ // Uncomment to enable trace
+ // admClnt.TraceOn(nil)
+
+ result, err := admClnt.ListDevices(context.Background(), rest.GetDevicesRequest{
+ Drives: []rest.Selector{}, // supports ellipsis. eg, `sda`, `sd{a...d}`, `sda,sd{b...d}`
+ Nodes: []rest.Selector{}, // supports ellipsis. eg, `node-1`, `node-{1...4}`, `node-1, node-{2...4}`
+ Statuses: []rest.DeviceStatus{}, // possible values are rest.DeviceStatusAvailable and rest.DeviceStatusUnavailable
+ })
+ if err != nil {
+ log.Fatalln(err)
+ }
+
+ for nodeName, deviceList := range result.DeviceInfo {
+ fmt.Printf("\n-------------------- Devices from Node: %s -----------------------------\n", nodeName)
+ for _, device := range deviceList {
+ fmt.Printf(" Device: %s", device.Name)
+ fmt.Printf("\n MajorMinor: %s", device.MajorMinor)
+ fmt.Printf("\n Size: %s", humanize.IBytes(device.Size))
+ fmt.Printf("\n Model: %s", device.Model)
+ fmt.Printf("\n Vendor: %s", device.Vendor)
+ fmt.Printf("\n Filesystem: %s", device.Filesystem)
+ fmt.Printf("\n Status: %s", device.Status)
+ fmt.Println("\n---------XX---------")
+ }
+ }
+}
diff --git a/pkg/rest/node-handlers.go b/pkg/rest/node-api-handlers.go
similarity index 100%
rename from pkg/rest/node-handlers.go
rename to pkg/rest/node-api-handlers.go
diff --git a/pkg/rest/node.go b/pkg/rest/node-api-server.go
similarity index 98%
rename from pkg/rest/node.go
rename to pkg/rest/node-api-server.go
index 78c194e89..3fbcd35b1 100644
--- a/pkg/rest/node.go
+++ b/pkg/rest/node-api-server.go
@@ -143,12 +143,12 @@ func (n *nodeAPIHandler) listLocalDevicesHandler(w http.ResponseWriter, r *http.
func (n *nodeAPIHandler) listLocalDevices(ctx context.Context, req GetDevicesRequest) ([]Device, error) {
var driveSelectors, statusSelectors []string
- var err error
- if len(req.Drives) > 0 {
- driveSelectors, err = ellipsis.Expand(string(req.Drives))
+ for _, driveSelector := range req.Drives {
+ expanded, err := ellipsis.Expand(string(driveSelector))
if err != nil {
- return nil, fmt.Errorf("couldn't expand the node selector %v: %v", req.Nodes, err)
+ return nil, fmt.Errorf("couldn't expand the drive selector %v: %v", driveSelector, err)
}
+ driveSelectors = append(driveSelectors, expanded...)
}
for _, status := range req.Statuses {
statusSelectors = append(statusSelectors, string(status))
diff --git a/pkg/rest/types.go b/pkg/rest/types.go
index 11d52d2cf..e33e55e32 100644
--- a/pkg/rest/types.go
+++ b/pkg/rest/types.go
@@ -33,8 +33,8 @@ const (
// GetDevicesRequest is the request type to fetch the devices present in the cluster
type GetDevicesRequest struct {
- Nodes Selector `json:"nodes,omitempty"`
- Drives Selector `json:"drives,omitempty"`
+ Nodes []Selector `json:"nodes,omitempty"`
+ Drives []Selector `json:"drives,omitempty"`
Statuses []DeviceStatus `json:"statuses,omitempty"`
}
diff --git a/pkg/rest/utils.go b/pkg/rest/utils.go
index 6a6018d43..7a1f78eb8 100644
--- a/pkg/rest/utils.go
+++ b/pkg/rest/utils.go
@@ -20,10 +20,12 @@ import (
"crypto/tls"
"encoding/json"
"errors"
+ "fmt"
"io"
"io/ioutil"
"net"
"net/http"
+ "net/url"
"os"
"path"
"strings"
@@ -31,6 +33,11 @@ import (
"github.com/minio/directpv/pkg/device"
"github.com/minio/directpv/pkg/types"
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+var (
+ errEmptyEndpointURL = errors.New("endpoint url is empty")
)
type matchFn func(drive *types.Drive, device *device.Device) bool
@@ -46,10 +53,7 @@ func getMatchedDevicesForDrive(drive *types.Drive, devices []*device.Device) ([]
}
func fsMatcher(drive *types.Drive, device *device.Device) bool {
- if drive.Status.FSUUID != device.FSUUID {
- return false
- }
- return true
+ return drive.Status.FSUUID == device.FSUUID
}
func getMatchedDevices(drive *types.Drive, devices []*device.Device, matchFn matchFn) (matchedDevices, unmatchedDevices []*device.Device) {
@@ -116,31 +120,40 @@ func (s *FormatDeviceStatus) setErr(err error, message, suggestion string) {
s.Suggestion = suggestion
}
-func getTransport() *http.Transport {
- // Keep TLS config.
- tlsConfig := &tls.Config{
- // Can't use SSLv3 because of POODLE and BEAST
- // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
- // Can't use TLSv1.1 because of RC4 cipher usage
- MinVersion: tls.VersionTLS12,
- InsecureSkipVerify: true, // FIXME: use trusted CA
- }
- return &http.Transport{
+func getDefaultTransport(secure bool) *http.Transport {
+ tr := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
- Timeout: 30 * time.Second,
+ Timeout: 50 * time.Second,
}).DialContext,
MaxIdleConnsPerHost: 1024,
- IdleConnTimeout: 30 * time.Second,
+ IdleConnTimeout: 50 * time.Second,
ResponseHeaderTimeout: 1 * time.Minute,
- TLSHandshakeTimeout: 5 * time.Second,
- ExpectContinueTimeout: 5 * time.Second,
- TLSClientConfig: tlsConfig,
+ TLSHandshakeTimeout: 10 * time.Second,
+ // ExpectContinueTimeout: 5 * time.Second,
// Go net/http automatically unzip if content-type is
// gzip disable this feature, as we are always interested
// in raw stream.
DisableCompression: true,
}
+ if secure {
+ // Keep TLS config.
+ tr.TLSClientConfig = &tls.Config{
+ // Can't use SSLv3 because of POODLE and BEAST
+ // Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
+ // Can't use TLSv1.1 because of RC4 cipher usage
+ MinVersion: tls.VersionTLS12,
+ InsecureSkipVerify: true, // FIXME: use trusted CA
+ }
+ }
+ return tr
+}
+
+// Close the response properly for the RoundTripper to re-use the connections
+func closeResponse(resp *http.Response) {
+ if resp != nil {
+ drainBody(resp.Body)
+ }
}
// drainBody close non nil response with any response Body.
@@ -162,3 +175,54 @@ func drainBody(respBody io.ReadCloser) {
io.Copy(ioutil.Discard, respBody)
}
}
+
+// getEndpointURL - construct a new endpoint.
+func getEndpointURL(endpoint string, secure bool) (*url.URL, error) {
+ if strings.Contains(endpoint, ":") {
+ host, _, err := net.SplitHostPort(endpoint)
+ if err != nil {
+ return nil, err
+ }
+ if !s3utils.IsValidIP(host) && !s3utils.IsValidDomain(host) {
+ return nil, fmt.Errorf("endpoint: %s does not follow ip address or domain name standards", endpoint)
+ }
+ } else {
+ if !s3utils.IsValidIP(endpoint) && !s3utils.IsValidDomain(endpoint) {
+ return nil, fmt.Errorf("endpoint: %s does not follow ip address or domain name standards", endpoint)
+ }
+ }
+
+ // If secure is false, use 'http' scheme.
+ scheme := "https"
+ if !secure {
+ scheme = "http"
+ }
+
+ // Construct a secured endpoint URL.
+ endpointURLStr := scheme + "://" + endpoint
+ endpointURL, err := url.Parse(endpointURLStr)
+ if err != nil {
+ return nil, err
+ }
+
+ // Validate incoming endpoint URL.
+ if err := isValidEndpointURL(endpointURL.String()); err != nil {
+ return nil, err
+ }
+ return endpointURL, nil
+}
+
+// Verify if input endpoint URL is valid.
+func isValidEndpointURL(endpointURL string) error {
+ if endpointURL == "" {
+ return errEmptyEndpointURL
+ }
+ url, err := url.Parse(endpointURL)
+ if err != nil {
+ return fmt.Errorf("endpoint url %s cannot be parsed", endpointURL)
+ }
+ if url.Path != "/" && url.Path != "" {
+ return fmt.Errorf("endpoint url %s cannot have fully qualified paths", endpointURL)
+ }
+ return nil
+}
diff --git a/pkg/rest/verify_sign_utils.go b/pkg/rest/verify_sign_utils.go
new file mode 100644
index 000000000..2034feb90
--- /dev/null
+++ b/pkg/rest/verify_sign_utils.go
@@ -0,0 +1,382 @@
+// This file is part of MinIO DirectPV
+// Copyright (c) 2021, 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package rest
+
+import (
+ "bytes"
+ "crypto/hmac"
+ "crypto/sha256"
+ "crypto/subtle"
+ "encoding/hex"
+ "errors"
+ "net/http"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/minio/minio-go/v7/pkg/s3utils"
+)
+
+const (
+ defaultSha256Cksum = "UNSIGNED-PAYLOAD"
+ authorizationHeader = "Authorization"
+ signV4Algorithm = "AWS4-HMAC-SHA256"
+ SlashSeparator = "/"
+ accessKeyMinLen = 3
+ yyyymmdd = "20060102"
+ amzDateHeaderKey = "X-Amz-Date"
+ dateHeaderKey = "Date"
+ iso8601Format = "20060102T150405Z"
+ contentTypeHeaderKey = "Content-Type"
+ contentMD5HeaderKey = "Content-Md5"
+)
+
+var (
+ errUnsignedHeaders = errors.New("unsigned headers found in the request")
+ errWrongAccessKey = errors.New("wrong access key")
+ errAuthHeaderEmpty = errors.New("auth header value is empty")
+ errSignatureVersionNotSupported = errors.New("signature version is not supported")
+ errMissingFields = errors.New("missing fields in the request")
+ errMissingCredTag = errors.New("missing cred tag")
+ errCredMalformed = errors.New("credential tag is malformed")
+ errInvalidAccessKeyID = errors.New("invalid access key id")
+ errMalformedCredentialDate = errors.New("date in the request is malformed")
+ errMissingSignHeadersTag = errors.New("missing sign headers")
+ errMissingSignTag = errors.New("missing sign tag")
+ errMissingDateHeader = errors.New("missing date header")
+ errMalformedDate = errors.New("malformed date value in the header")
+ errSignatureDoesNotMatch = errors.New("signature doesn't match")
+)
+
+// credentialHeader data type represents structured form of Credential string from authorization header.
+type credentialHeader struct {
+ accessKey string
+ scope struct {
+ date time.Time
+ region string
+ service string
+ request string
+ }
+}
+
+// Return scope string.
+func (c credentialHeader) getScope() string {
+ return strings.Join([]string{
+ c.scope.date.Format(yyyymmdd),
+ c.scope.region,
+ c.scope.service,
+ c.scope.request,
+ }, SlashSeparator)
+}
+
+// signValues data type represents structured form of AWS Signature V4 header.
+type signValues struct {
+ Credential credentialHeader
+ SignedHeaders []string
+ Signature string
+}
+
+// extractSignedHeaders extract signed headers from Authorization header
+func extractSignedHeaders(signedHeaders []string, r *http.Request) (http.Header, error) {
+ reqHeaders := r.Header
+ reqQueries := r.Form
+ // find whether "host" is part of list of signed headers.
+ // if not return ErrUnsignedHeaders. "host" is mandatory.
+ if !contains(signedHeaders, "host") {
+ return nil, errUnsignedHeaders
+ }
+ extractedSignedHeaders := make(http.Header)
+ for _, header := range signedHeaders {
+ // `host` will not be found in the headers, can be found in r.Host.
+ // but its alway necessary that the list of signed headers containing host in it.
+ val, ok := reqHeaders[http.CanonicalHeaderKey(header)]
+ if !ok {
+ // try to set headers from Query String
+ val, ok = reqQueries[header]
+ }
+ if ok {
+ extractedSignedHeaders[http.CanonicalHeaderKey(header)] = val
+ continue
+ }
+ switch header {
+ case "expect":
+ // Golang http server strips off 'Expect' header, if the
+ // client sent this as part of signed headers we need to
+ // handle otherwise we would see a signature mismatch.
+ // `aws-cli` sets this as part of signed headers.
+ //
+ // According to
+ // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.20
+ // Expect header is always of form:
+ //
+ // Expect = "Expect" ":" 1#expectation
+ // expectation = "100-continue" | expectation-extension
+ //
+ // So it safe to assume that '100-continue' is what would
+ // be sent, for the time being keep this work around.
+ // Adding a *TODO* to remove this later when Golang server
+ // doesn't filter out the 'Expect' header.
+ extractedSignedHeaders.Set(header, "100-continue")
+ case "host":
+ // Go http server removes "host" from Request.Header
+ extractedSignedHeaders.Set(header, r.Host)
+ case "transfer-encoding":
+ // Go http server removes "host" from Request.Header
+ extractedSignedHeaders[http.CanonicalHeaderKey(header)] = r.TransferEncoding
+ case "content-length":
+ // Signature-V4 spec excludes Content-Length from signed headers list for signature calculation.
+ // But some clients deviate from this rule. Hence we consider Content-Length for signature
+ // calculation to be compatible with such clients.
+ extractedSignedHeaders.Set(header, strconv.FormatInt(r.ContentLength, 10))
+ default:
+ return nil, errUnsignedHeaders
+ }
+ }
+ return extractedSignedHeaders, nil
+}
+
+func contains(slice interface{}, elem interface{}) bool {
+ v := reflect.ValueOf(slice)
+ if v.Kind() == reflect.Slice {
+ for i := 0; i < v.Len(); i++ {
+ if v.Index(i).Interface() == elem {
+ return true
+ }
+ }
+ }
+ return false
+}
+
+func parseSignV4(v4Auth string) (sv signValues, err error) {
+ // credElement is fetched first to skip replacing the space in access key.
+ credElement := strings.TrimPrefix(strings.Split(strings.TrimSpace(v4Auth), ",")[0], signV4Algorithm)
+ // Replace all spaced strings, some clients can send spaced
+ // parameters and some won't. So we pro-actively remove any spaces
+ // to make parsing easier.
+ v4Auth = strings.ReplaceAll(v4Auth, " ", "")
+ if v4Auth == "" {
+ return sv, errAuthHeaderEmpty
+ }
+
+ // Verify if the header algorithm is supported or not.
+ if !strings.HasPrefix(v4Auth, signV4Algorithm) {
+ return sv, errSignatureVersionNotSupported
+ }
+
+ // Strip off the Algorithm prefix.
+ v4Auth = strings.TrimPrefix(v4Auth, signV4Algorithm)
+ authFields := strings.Split(strings.TrimSpace(v4Auth), ",")
+ if len(authFields) != 3 {
+ return sv, errMissingFields
+ }
+
+ // Initialize signature version '4' structured header.
+ signV4Values := signValues{}
+
+ // Save credentail values.
+ signV4Values.Credential, err = parseCredentialHeader(strings.TrimSpace(credElement))
+ if err != nil {
+ return sv, err
+ }
+
+ // Save signed headers.
+ signV4Values.SignedHeaders, err = parseSignedHeader(authFields[1])
+ if err != nil {
+ return sv, err
+ }
+
+ // Save signature.
+ signV4Values.Signature, err = parseSignature(authFields[2])
+ if err != nil {
+ return sv, err
+ }
+
+ // Return the structure here.
+ return signV4Values, nil
+}
+
+// parse credentialHeader string into its structured form.
+func parseCredentialHeader(credElement string) (ch credentialHeader, err error) {
+ creds := strings.SplitN(strings.TrimSpace(credElement), "=", 2)
+ if len(creds) != 2 {
+ return ch, errMissingFields
+ }
+ if creds[0] != "Credential" {
+ return ch, errMissingCredTag
+ }
+ credElements := strings.Split(strings.TrimSpace(creds[1]), SlashSeparator)
+ if len(credElements) < 5 {
+ return ch, errCredMalformed
+ }
+ accessKey := strings.Join(credElements[:len(credElements)-4], SlashSeparator) // The access key may contain one or more `/`
+ if len(accessKey) <= accessKeyMinLen {
+ return ch, errInvalidAccessKeyID
+ }
+ // Save access key id.
+ cred := credentialHeader{
+ accessKey: accessKey,
+ }
+ credElements = credElements[len(credElements)-4:]
+ var e error
+ cred.scope.date, e = time.Parse(yyyymmdd, credElements[0])
+ if e != nil {
+ return ch, errMalformedCredentialDate
+ }
+ cred.scope.service = credElements[2]
+ cred.scope.request = credElements[3]
+ return cred, nil
+}
+
+// Parse slice of signed headers from signed headers tag.
+func parseSignedHeader(signedHdrElement string) ([]string, error) {
+ signedHdrFields := strings.Split(strings.TrimSpace(signedHdrElement), "=")
+ if len(signedHdrFields) != 2 {
+ return nil, errMissingFields
+ }
+ if signedHdrFields[0] != "SignedHeaders" {
+ return nil, errMissingSignHeadersTag
+ }
+ if signedHdrFields[1] == "" {
+ return nil, errMissingFields
+ }
+ signedHeaders := strings.Split(signedHdrFields[1], ";")
+ return signedHeaders, nil
+}
+
+// Parse signature from signature tag.
+func parseSignature(signElement string) (string, error) {
+ signFields := strings.Split(strings.TrimSpace(signElement), "=")
+ if len(signFields) != 2 {
+ return "", errMissingFields
+ }
+ if signFields[0] != "Signature" {
+ return "", errMissingSignTag
+ }
+ if signFields[1] == "" {
+ return "", errMissingFields
+ }
+ signature := signFields[1]
+ return signature, nil
+}
+
+// getCanonicalRequest generate a canonical request of style
+//
+// canonicalRequest =
+//
+// \n
+// \n
+// \n
+// \n
+// \n
+//
+func getCanonicalRequest(extractedSignedHeaders http.Header, queryStr, urlPath, method string) string {
+ rawQuery := strings.ReplaceAll(queryStr, "+", "%20")
+ encodedPath := s3utils.EncodePath(urlPath)
+ canonicalRequest := strings.Join([]string{
+ method,
+ encodedPath,
+ rawQuery,
+ getCanonicalHeaders(extractedSignedHeaders),
+ getSignedHeaders(extractedSignedHeaders),
+ defaultSha256Cksum,
+ }, "\n")
+ return canonicalRequest
+}
+
+// getCanonicalHeaders generate a list of request headers with their values
+func getCanonicalHeaders(signedHeaders http.Header) string {
+ var headers []string
+ vals := make(http.Header)
+ for k, vv := range signedHeaders {
+ headers = append(headers, strings.ToLower(k))
+ vals[strings.ToLower(k)] = vv
+ }
+ sort.Strings(headers)
+
+ var buf bytes.Buffer
+ for _, k := range headers {
+ buf.WriteString(k)
+ buf.WriteByte(':')
+ for idx, v := range vals[k] {
+ if idx > 0 {
+ buf.WriteByte(',')
+ }
+ buf.WriteString(signV4TrimAll(v))
+ }
+ buf.WriteByte('\n')
+ }
+ return buf.String()
+}
+
+// Trim leading and trailing spaces and replace sequential spaces with one space, following Trimall()
+// in http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
+func signV4TrimAll(input string) string {
+ // Compress adjacent spaces (a space is determined by
+ // unicode.IsSpace() internally here) to one space and return
+ return strings.Join(strings.Fields(input), " ")
+}
+
+// getSignedHeaders generate a string i.e alphabetically sorted, semicolon-separated list of lowercase request header names
+func getSignedHeaders(signedHeaders http.Header) string {
+ var headers []string
+ for k := range signedHeaders {
+ headers = append(headers, strings.ToLower(k))
+ }
+ sort.Strings(headers)
+ return strings.Join(headers, ";")
+}
+
+// getStringToSign a string based on selected query values.
+func getStringToSign(canonicalRequest string, t time.Time, scope string) string {
+ stringToSign := signV4Algorithm + "\n" + t.Format(iso8601Format) + "\n"
+ stringToSign += scope + "\n"
+ canonicalRequestBytes := sha256.Sum256([]byte(canonicalRequest))
+ stringToSign += hex.EncodeToString(canonicalRequestBytes[:])
+ return stringToSign
+}
+
+// getSigningKey hmac seed to calculate final signature.
+func getSigningKey(secretKey string, t time.Time, region, stype string) []byte {
+ date := sumHMAC([]byte("AWS4"+secretKey), []byte(t.Format(yyyymmdd)))
+ regionBytes := sumHMAC(date, []byte(region))
+ service := sumHMAC(regionBytes, []byte(stype))
+ signingKey := sumHMAC(service, []byte("aws4_request"))
+ return signingKey
+}
+
+// sumHMAC calculate hmac between two input byte array.
+func sumHMAC(key []byte, data []byte) []byte {
+ hash := hmac.New(sha256.New, key)
+ hash.Write(data)
+ return hash.Sum(nil)
+}
+
+// getSignature final signature in hexadecimal form.
+func getSignature(signingKey []byte, stringToSign string) string {
+ return hex.EncodeToString(sumHMAC(signingKey, []byte(stringToSign)))
+}
+
+// compareSignatureV4 returns true if and only if both signatures
+// are equal. The signatures are expected to be HEX encoded strings
+// according to the AWS S3 signature V4 spec.
+func compareSignatureV4(sig1, sig2 string) bool {
+ // The CTC using []byte(str) works because the hex encoding
+ // is unique for a sequence of bytes. See also compareSignatureV2.
+ return subtle.ConstantTimeCompare([]byte(sig1), []byte(sig2)) == 1
+}
diff --git a/pkg/types/aliases.go b/pkg/types/aliases.go
index 685b5656f..5b9436825 100644
--- a/pkg/types/aliases.go
+++ b/pkg/types/aliases.go
@@ -29,6 +29,7 @@ var Versions = []string{
}
var LatestAddToScheme = directpv.AddToScheme
+
type Interface = typeddirectpv.DirectpvV1beta1Interface
type Client = typeddirectpv.DirectpvV1beta1Client