diff --git a/go.mod b/go.mod index 0ffa8edd9..6e025c706 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect github.com/aws/aws-sdk-go v1.37.24 github.com/go-logr/logr v0.1.0 + github.com/minio/minio-go/v7 v7.0.10 github.com/onsi/ginkgo v1.12.0 github.com/onsi/gomega v1.9.0 github.com/operator-framework/operator-sdk v0.18.2 diff --git a/go.sum b/go.sum index 0dc9f28c5..cbcc76ff1 100644 --- a/go.sum +++ b/go.sum @@ -604,6 +604,8 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jsonnet-bundler/jsonnet-bundler v0.3.1/go.mod h1:/by7P/OoohkI3q4CgSFqcoFsVY+IaNbzOVDknEsKDeU= 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= @@ -621,6 +623,9 @@ github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= +github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= 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.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -701,12 +706,20 @@ github.com/miekg/dns v1.1.22/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RRf2RRxieJU= github.com/mikefarah/yq/v2 v2.4.1/go.mod h1:i8SYf1XdgUvY2OFwSqGAtWOOgimD2McJ6iutoxRm4k0= github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= +github.com/minio/md5-simd v1.1.0 h1:QPfiOqlZH+Cj9teu0t9b1nTBfPbyTl16Of5MeuShdK4= +github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw= +github.com/minio/minio-go v1.0.0 h1:ooSujki+Z1PRGZsYffJw5jnF5eMBvzMVV86TLAlM0UM= +github.com/minio/minio-go v6.0.14+incompatible h1:fnV+GD28LeqdN6vT2XdGKW8Qe/IfjJDswNVuni6km9o= github.com/minio/minio-go/v6 v6.0.49/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= +github.com/minio/minio-go/v7 v7.0.10 h1:1oUKe4EOPUEhw2qnPQaPsJ0lmVTYLFu03SiItauXs94= +github.com/minio/minio-go/v7 v7.0.10/go.mod h1:td4gW1ldOsj1PbSNS+WYK43j+P1XVhX/8W8awaYlBFo= +github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKUJU= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -883,6 +896,8 @@ github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rubenv/sql-migrate v0.0.0-20200212082348-64f95ea68aa3/go.mod h1:rtQlpHw+eR6UrqaS3kX1VYeaCxzCVdimDS7g5Ln4pPc= github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1072,6 +1087,7 @@ golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEB golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1142,6 +1158,7 @@ golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1211,6 +1228,8 @@ golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= @@ -1354,6 +1373,8 @@ 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.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww= +gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= diff --git a/pkg/splunk/client/awss3client.go b/pkg/splunk/client/awss3client.go index 95b7a3175..795e5a75c 100644 --- a/pkg/splunk/client/awss3client.go +++ b/pkg/splunk/client/awss3client.go @@ -2,6 +2,8 @@ package client import ( "encoding/json" + "fmt" + "regexp" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -20,10 +22,21 @@ type AWSS3Client struct { AWSSecretAccessKey string Prefix string StartAfter string + Endpoint string +} + +// regex to extract the region from the s3 endpoint +var regionRegex = ".*.s3[-,.](?P.*).amazonaws.com" + +//getRegion extracts the region from the endpoint field +func getRegion(endpoint string) string { + pattern := regexp.MustCompile(regionRegex) + return pattern.FindStringSubmatch(endpoint)[1] } // NewAWSS3Client returns an AWS S3 client -func NewAWSS3Client(region string, bucketName string, accessKeyID string, secretAccessKey string, prefix string, startAfter string) S3Client { +func NewAWSS3Client(bucketName string, accessKeyID string, secretAccessKey string, prefix string, startAfter string, endpoint string) S3Client { + region := getRegion(endpoint) return &AWSS3Client{ Region: region, BucketName: bucketName, @@ -31,6 +44,7 @@ func NewAWSS3Client(region string, bucketName string, accessKeyID string, secret AWSSecretAccessKey: secretAccessKey, Prefix: prefix, StartAfter: startAfter, + Endpoint: endpoint, } } @@ -90,3 +104,13 @@ func (awsclient *AWSS3Client) GetAppsList() (S3Response, error) { return s3Resp, nil } + +// GetInitContainerImage returns the initContainer image to be used with this s3 client +func (awsclient *AWSS3Client) GetInitContainerImage() string { + return ("amazon/aws-cli") +} + +// GetInitContainerCmd returns the init container command on a per app source basis to be used by the initContainer +func (awsclient *AWSS3Client) GetInitContainerCmd(endpoint string, bucket string, path string, appSrcName string, appMnt string) []string { + return ([]string{fmt.Sprintf("--endpoint-url=%s", endpoint), "s3", "sync", fmt.Sprintf("s3://%s/%s", bucket, path), fmt.Sprintf("%s/%s", appMnt, appSrcName)}) +} diff --git a/pkg/splunk/client/minioclient.go b/pkg/splunk/client/minioclient.go new file mode 100644 index 000000000..2624acfa3 --- /dev/null +++ b/pkg/splunk/client/minioclient.go @@ -0,0 +1,117 @@ +package client + +import ( + "context" + "fmt" + "strings" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +// blank assignment to verify that MinioClient implements S3Client +var _ S3Client = &MinioClient{} + +// MinioClient is a client to implement S3 specific APIs +type MinioClient struct { + BucketName string + S3AccessKeyID string + S3SecretAccessKey string + Prefix string + StartAfter string + Endpoint string +} + +// NewMinioClient returns an Minio client +func NewMinioClient(bucketName string, accessKeyID string, secretAccessKey string, prefix string, startAfter string, endpoint string) S3Client { + return &MinioClient{ + BucketName: bucketName, + S3AccessKeyID: accessKeyID, + S3SecretAccessKey: secretAccessKey, + Prefix: prefix, + StartAfter: startAfter, + Endpoint: endpoint, + } +} + +//RegisterMinioClient will add the corresponding function pointer to the map +func RegisterMinioClient() { + S3Clients["minio"] = NewMinioClient +} + +// InitMinioClientSession initializes and returns a client session object +func InitMinioClientSession(appS3Endpoint string, accessKeyID string, secretAccessKey string, isNotInTestContext *bool) (*minio.Client, error) { + scopedLog := log.WithName("InitMinioClientSession") + *isNotInTestContext = true + + // Check if SSL is needed + useSSL := true + if strings.HasPrefix(appS3Endpoint, "http://") { + useSSL = false + appS3Endpoint = strings.TrimPrefix(appS3Endpoint, "http://") + } else if strings.HasPrefix(appS3Endpoint, "https://") { + appS3Endpoint = strings.TrimPrefix(appS3Endpoint, "https://") + } else { + // Unsupported endpoint + scopedLog.Info("Unsupported endpoint for Minio S3 client", "appS3Endpoint", appS3Endpoint) + return nil, nil + } + + // New returns an Minio compatible client object. API compatibility (v2 or v4) is automatically + // determined based on the Endpoint value. + scopedLog.Info("Connecting to Minio S3 for apps", "appS3Endpoint", appS3Endpoint) + s3Client, err := minio.New(appS3Endpoint, &minio.Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: useSSL, + }) + if err != nil { + scopedLog.Info("Error creating new Minio Client Session", "err", err) + } + + return s3Client, err +} + +// GetAppsList get the list of apps from remote storage +func (client *MinioClient) GetAppsList() (S3Response, error) { + scopedLog := log.WithName("GetAppsList") + + scopedLog.Info("Getting Apps list", " S3 Bucket", client.BucketName) + s3Resp := S3Response{} + appS3Endpoint := client.Endpoint + + isNotInTestContext := true + s3Client, err := InitMinioClientSession(appS3Endpoint, client.S3AccessKeyID, client.S3SecretAccessKey, &isNotInTestContext) + if err != nil { + scopedLog.Info("Error creating new Minio Client Session", "err", err) + return s3Resp, err + } + + // Create a bucket list command for all files in bucket + opts := minio.ListObjectsOptions{ + UseV1: true, + Prefix: client.Prefix, + Recursive: false, + } + + // List all objects from a bucket-name with a matching prefix. + for object := range s3Client.ListObjects(context.Background(), client.BucketName, opts) { + if object.Err != nil { + scopedLog.Info("Got an object error", "object.Err", object.Err, "client.BucketName", client.BucketName) + return s3Resp, nil + } + scopedLog.Info("Got an object", "object", object) + s3Resp.Objects = append(s3Resp.Objects, &RemoteObject{Etag: &object.ETag, Key: &object.Key, LastModified: &object.LastModified, Size: &object.Size, StorageClass: &object.StorageClass}) + } + + return s3Resp, nil +} + +// GetInitContainerImage returns the initContainer image to be used with this s3 client +func (client *MinioClient) GetInitContainerImage() string { + return ("amazon/aws-cli") +} + +// GetInitContainerCmd returns the init container command on a per app source basis to be used by the initContainer +func (client *MinioClient) GetInitContainerCmd(endpoint string, bucket string, path string, appSrcName string, appMnt string) []string { + return ([]string{fmt.Sprintf("--endpoint-url=%s", endpoint), "s3", "sync", fmt.Sprintf("s3://%s/%s", bucket, path), fmt.Sprintf("%s/%s", appMnt, appSrcName)}) +} diff --git a/pkg/splunk/client/s3client.go b/pkg/splunk/client/s3client.go index 9aa8b5f5f..5c195bb37 100644 --- a/pkg/splunk/client/s3client.go +++ b/pkg/splunk/client/s3client.go @@ -6,8 +6,8 @@ import ( ) //GetS3Client gets the required S3Client based on the provider -type GetS3Client func(string /* region */, string /* bucket */, string, /* AWS access key ID */ - string /* AWS secret access key */, string /* Prefix */, string /* StartAfter */) S3Client +type GetS3Client func(string /* bucket */, string, /* AWS access key ID */ + string /* AWS secret access key */, string /* Prefix */, string /* StartAfter */, string /* Endpoint */) S3Client // S3Clients is a map of provider name to init functions var S3Clients = make(map[string]GetS3Client) @@ -15,6 +15,8 @@ var S3Clients = make(map[string]GetS3Client) // S3Client is an interface to implement different S3 client APIs type S3Client interface { GetAppsList() (S3Response, error) + GetInitContainerImage() string + GetInitContainerCmd(string /* endpoint */, string /* bucket */, string /* path */, string /* app src name */, string /* app mnt */) []string } // S3Response struct contains list of RemoteObject objects as part of S3 response @@ -36,6 +38,8 @@ func RegisterS3Client(provider string) { switch provider { case "aws": RegisterAWSS3Client() + case "minio": + RegisterMinioClient() default: fmt.Println("ERROR: Invalid provider specified: ", provider) } diff --git a/pkg/splunk/enterprise/clustermaster.go b/pkg/splunk/enterprise/clustermaster.go index 323a7cdd1..30585ec61 100644 --- a/pkg/splunk/enterprise/clustermaster.go +++ b/pkg/splunk/enterprise/clustermaster.go @@ -209,6 +209,9 @@ func getClusterMasterStatefulSet(client splcommon.ControllerClient, cr *enterpri setupInitContainer(&ss.Spec.Template, cr.Spec.Image, cr.Spec.ImagePullPolicy, commandForCMSmartstore) } + // Setup App framework init containers + setupAppInitContainers(client, cr, &ss.Spec.Template, &cr.Spec.AppFrameworkConfig) + return ss, err } diff --git a/pkg/splunk/enterprise/licensemaster.go b/pkg/splunk/enterprise/licensemaster.go index 7a2b9b4f8..132321aec 100644 --- a/pkg/splunk/enterprise/licensemaster.go +++ b/pkg/splunk/enterprise/licensemaster.go @@ -134,7 +134,15 @@ func ApplyLicenseMaster(client splcommon.ControllerClient, cr *enterprisev1.Lice // getLicenseMasterStatefulSet returns a Kubernetes StatefulSet object for a Splunk Enterprise license master. func getLicenseMasterStatefulSet(client splcommon.ControllerClient, cr *enterprisev1.LicenseMaster) (*appsv1.StatefulSet, error) { - return getSplunkStatefulSet(client, cr, &cr.Spec.CommonSplunkSpec, SplunkLicenseMaster, 1, []corev1.EnvVar{}) + ss, err := getSplunkStatefulSet(client, cr, &cr.Spec.CommonSplunkSpec, SplunkLicenseMaster, 1, []corev1.EnvVar{}) + if err != nil { + return ss, err + } + + // Setup App framework init containers + setupAppInitContainers(client, cr, &ss.Spec.Template, &cr.Spec.AppFrameworkConfig) + + return ss, err } // validateLicenseMasterSpec checks validity and makes default updates to a LicenseMasterSpec, and returns error if something is wrong. diff --git a/pkg/splunk/enterprise/names.go b/pkg/splunk/enterprise/names.go index fb4ce6903..062a759be 100644 --- a/pkg/splunk/enterprise/names.go +++ b/pkg/splunk/enterprise/names.go @@ -41,6 +41,9 @@ const ( // identifier smartstoreTemplateStr = "splunk-%s-%s-smartstore" + // init container name + initContainerTemplate = "%s-init-%d-%s" + // default docker image used for Splunk instances defaultSplunkImage = "splunk/splunk" @@ -79,6 +82,12 @@ const ( protoHTTP = "http" protoHTTPS = "https" protoTCP = "tcp" + + // Volume name for shared volume between init and splunk containers + appVolumeMntName = "init-apps" + + // Mount location for the shared app package volume + appBktMnt = "/init-apps/" ) // GetSplunkDeploymentName uses a template to name a Kubernetes Deployment for Splunk instances. diff --git a/pkg/splunk/enterprise/searchheadcluster.go b/pkg/splunk/enterprise/searchheadcluster.go index 9f84f9366..78cb5dd7e 100644 --- a/pkg/splunk/enterprise/searchheadcluster.go +++ b/pkg/splunk/enterprise/searchheadcluster.go @@ -539,7 +539,15 @@ func getSearchHeadStatefulSet(client splcommon.ControllerClient, cr *enterprisev // getDeployerStatefulSet returns a Kubernetes StatefulSet object for a Splunk Enterprise license master. func getDeployerStatefulSet(client splcommon.ControllerClient, cr *enterprisev1.SearchHeadCluster) (*appsv1.StatefulSet, error) { - return getSplunkStatefulSet(client, cr, &cr.Spec.CommonSplunkSpec, SplunkDeployer, 1, getSearchHeadExtraEnv(cr, cr.Spec.Replicas)) + ss, err := getSplunkStatefulSet(client, cr, &cr.Spec.CommonSplunkSpec, SplunkDeployer, 1, getSearchHeadExtraEnv(cr, cr.Spec.Replicas)) + if err != nil { + return ss, err + } + + // Setup App framework init containers + setupAppInitContainers(client, cr, &ss.Spec.Template, &cr.Spec.AppFrameworkConfig) + + return ss, err } // validateSearchHeadClusterSpec checks validity and makes default updates to a SearchHeadClusterSpec, and returns error if something is wrong. diff --git a/pkg/splunk/enterprise/standalone.go b/pkg/splunk/enterprise/standalone.go index c91260bd2..4845222b7 100644 --- a/pkg/splunk/enterprise/standalone.go +++ b/pkg/splunk/enterprise/standalone.go @@ -177,6 +177,9 @@ func getStandaloneStatefulSet(client splcommon.ControllerClient, cr *enterprisev setupInitContainer(&ss.Spec.Template, cr.Spec.Image, cr.Spec.ImagePullPolicy, commandForStandaloneSmartstore) } + // Setup App framework init containers + setupAppInitContainers(client, cr, &ss.Spec.Template, &cr.Spec.AppFrameworkConfig) + return ss, nil } diff --git a/pkg/splunk/enterprise/util.go b/pkg/splunk/enterprise/util.go index c656b67f7..638d7bc6d 100644 --- a/pkg/splunk/enterprise/util.go +++ b/pkg/splunk/enterprise/util.go @@ -16,7 +16,6 @@ package enterprise import ( "fmt" - "regexp" "strings" "time" @@ -34,15 +33,6 @@ import ( // kubernetes logger used by splunk.enterprise package var log = logf.Log.WithName("splunk.enterprise") -// regex to extract the region from the s3 endpoint -var regionRegex = ".*.s3[-,.](?P.*).amazonaws.com" - -//getRegion extracts the region from the endpoint field -func getRegion(endpoint string) string { - pattern := regexp.MustCompile(regionRegex) - return pattern.FindStringSubmatch(endpoint)[1] -} - // GetRemoteStorageClient returns the corresponding S3Client func GetRemoteStorageClient(client splcommon.ControllerClient, cr splcommon.MetaObject, appFrameworkRef *enterprisev1.AppFrameworkSpec, vol *enterprisev1.VolumeSpec, location string) (splclient.S3Client, error) { @@ -70,16 +60,13 @@ func GetRemoteStorageClient(client splcommon.ControllerClient, cr splcommon.Meta return nil, err } - // Get region from "endpoint" field - region := getRegion(vol.Endpoint) - // Get the bucket name form the "path" field bucket := strings.Split(vol.Path, "/")[0] //Get the prefix from the "path" field prefix := strings.TrimPrefix(vol.Path, bucket+"/") + location - return getClient(region, bucket, accessKeyID, secretAccessKey, prefix, prefix /* startAfter*/), nil + return getClient(bucket, accessKeyID, secretAccessKey, prefix, prefix /* startAfter*/, vol.Endpoint), nil } // ApplySplunkConfig reconciles the state of Kubernetes Secrets, ConfigMaps and other general settings for Splunk Enterprise instances. @@ -630,3 +617,102 @@ func markAppsStatusToComplete(appSrcDeplymentStatus map[string]enterprisev1.AppS return err } + +// setupAppInitContainers creates the necessary shared volume and init containers to download all +// app packages in the appSources configured and make them locally available to the Splunk instance. +func setupAppInitContainers(client splcommon.ControllerClient, cr splcommon.MetaObject, podTemplateSpec *corev1.PodTemplateSpec, appFrameworkConfig *enterprisev1.AppFrameworkSpec) { + scopedLog := log.WithName("setupAppInitContainers") + // Create shared volume and init containers for App Framework + if len(appFrameworkConfig.AppSources) > 0 { + // Create volume to shared between init and Splunk container to contain downloaded apps + emptyVolumeSource := corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + } + + initVol := corev1.Volume{ + Name: appVolumeMntName, + VolumeSource: emptyVolumeSource, + } + + podTemplateSpec.Spec.Volumes = append(podTemplateSpec.Spec.Volumes, initVol) + + // Add init apps mount to Splunk container + initVolumeSpec := corev1.VolumeMount{ + Name: appVolumeMntName, + MountPath: appBktMnt, + } + + // This assumes the Splunk instance container is Containers[0], which I *believe* is valid + podTemplateSpec.Spec.Containers[0].VolumeMounts = append(podTemplateSpec.Spec.Containers[0].VolumeMounts, initVolumeSpec) + + // Add app framework init containers per app source and attach the init volume + for i, appSrc := range appFrameworkConfig.AppSources { + // Get volume info from appSrc + volSpecPos, err := checkIfVolumeExists(appFrameworkConfig.VolList, appSrc.VolName) + if err != nil { + // Invalid appFramework config. This shouldn't happen + scopedLog.Info("Invalid appSrc volume spec, moving to the next one", "appSrc.VolName", appSrc.VolName, "err", err) + continue + } + appRepoVol := appFrameworkConfig.VolList[volSpecPos] + + // Use the provider name to get the corresponding function pointer + s3Client, err := GetRemoteStorageClient(client, cr, appFrameworkConfig, &appRepoVol, appSrc.Location) + if err != nil { + // move on to the next appSource if we are not able to get the required client + scopedLog.Info("Invalid Remote Storage Client", "appRepoVol.Name", appRepoVol.Name, "err", err) + continue + } + + // Prepare app source/repo values + appBkt := appRepoVol.Path + appS3Endpoint := appRepoVol.Endpoint + appSecretRef := appRepoVol.SecretRef + appSrcName := appSrc.Name + appSrcPath := appSrc.Location + appSrcScope := appSrc.Scope + initContainerName := fmt.Sprintf(initContainerTemplate, appSrcName, i, appSrcScope) + + // Setup init container + initContainerSpec := corev1.Container{ + Image: s3Client.GetInitContainerImage(), + ImagePullPolicy: "IfNotPresent", + Name: initContainerName, + Args: s3Client.GetInitContainerCmd(appS3Endpoint, appBkt, appSrcPath, appSrcName, appBktMnt), + Env: []corev1.EnvVar{ + { + Name: "AWS_ACCESS_KEY_ID", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: appSecretRef, + }, + Key: s3AccessKey, + }, + }, + }, + { + Name: "AWS_SECRET_ACCESS_KEY", + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: appSecretRef, + }, + Key: s3SecretKey, + }, + }, + }, + }, + } + + // Add mount to initContainer, same mount used for Splunk instance container as well + initContainerSpec.VolumeMounts = []corev1.VolumeMount{ + { + Name: appVolumeMntName, + MountPath: appBktMnt, + }, + } + podTemplateSpec.Spec.InitContainers = append(podTemplateSpec.Spec.InitContainers, initContainerSpec) + } + } +}