summaryrefslogtreecommitdiffstats
path: root/src/k8splugin/internal
diff options
context:
space:
mode:
Diffstat (limited to 'src/k8splugin/internal')
-rw-r--r--src/k8splugin/internal/db/mongo.go38
-rw-r--r--src/k8splugin/internal/db/mongo_test.go66
-rw-r--r--src/k8splugin/internal/helm/helm.go259
-rw-r--r--src/k8splugin/internal/helm/helm_test.go195
-rw-r--r--src/k8splugin/internal/rb/definition.go43
-rw-r--r--src/k8splugin/internal/rb/definition_test.go89
-rw-r--r--src/k8splugin/internal/rb/profile.go8
-rw-r--r--src/k8splugin/internal/rb/profile_yaml.go109
-rw-r--r--src/k8splugin/internal/rb/profile_yaml_test.go139
9 files changed, 887 insertions, 59 deletions
diff --git a/src/k8splugin/internal/db/mongo.go b/src/k8splugin/internal/db/mongo.go
index 05976b12..d414f543 100644
--- a/src/k8splugin/internal/db/mongo.go
+++ b/src/k8splugin/internal/db/mongo.go
@@ -17,14 +17,15 @@
package db
import (
- "github.com/mongodb/mongo-go-driver/bson"
- "github.com/mongodb/mongo-go-driver/bson/primitive"
- "github.com/mongodb/mongo-go-driver/mongo"
- "github.com/mongodb/mongo-go-driver/mongo/options"
- pkgerrors "github.com/pkg/errors"
"golang.org/x/net/context"
"log"
"os"
+
+ pkgerrors "github.com/pkg/errors"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/bson/primitive"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
)
// MongoCollection defines the a subset of MongoDB operations
@@ -39,7 +40,7 @@ type MongoCollection interface {
DeleteOne(ctx context.Context, filter interface{},
opts ...*options.DeleteOptions) (*mongo.DeleteResult, error)
Find(ctx context.Context, filter interface{},
- opts ...*options.FindOptions) (mongo.Cursor, error)
+ opts ...*options.FindOptions) (*mongo.Cursor, error)
}
// MongoStore is an implementation of the db.Store interface
@@ -60,12 +61,25 @@ var decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) {
return sr.DecodeBytes()
}
+// These exists only for allowing us to mock the cursor.Next function
+// Mainly because we cannot construct a mongo.Cursor struct from our
+// tests. All fields in that struct are private and there is no public
+// constructor method.
+var cursorNext = func(ctx context.Context, cursor *mongo.Cursor) bool {
+ return cursor.Next(ctx)
+}
+var cursorClose = func(ctx context.Context, cursor *mongo.Cursor) error {
+ return cursor.Close(ctx)
+}
+
// NewMongoStore initializes a Mongo Database with the name provided
// If a database with that name exists, it will be returned
func NewMongoStore(name string, store *mongo.Database) (Store, error) {
if store == nil {
ip := "mongodb://" + os.Getenv("DATABASE_IP") + ":27017"
- mongoClient, err := mongo.NewClient(ip)
+ clientOptions := options.Client()
+ clientOptions.ApplyURI(ip)
+ mongoClient, err := mongo.NewClient(clientOptions)
if err != nil {
return nil, err
}
@@ -292,16 +306,12 @@ func (m *MongoStore) ReadAll(coll, tag string) (map[string][]byte, error) {
if err != nil {
return nil, pkgerrors.Errorf("Error reading from database: %s", err.Error())
}
- defer cursor.Close(ctx)
+ defer cursorClose(ctx, cursor)
//Iterate over all the master tables
result := make(map[string][]byte)
- for cursor.Next(ctx) {
- d, err := cursor.DecodeBytes()
- if err != nil {
- log.Printf("Unable to decode data in Readall: %s", err.Error())
- continue
- }
+ for cursorNext(ctx, cursor) {
+ d := cursor.Current
//Read key of each master table
key, ok := d.Lookup("key").StringValueOK()
diff --git a/src/k8splugin/internal/db/mongo_test.go b/src/k8splugin/internal/db/mongo_test.go
index 1663e774..973921c3 100644
--- a/src/k8splugin/internal/db/mongo_test.go
+++ b/src/k8splugin/internal/db/mongo_test.go
@@ -21,43 +21,21 @@ package db
import (
"bytes"
"context"
- "github.com/mongodb/mongo-go-driver/bson"
- "github.com/mongodb/mongo-go-driver/mongo"
- "github.com/mongodb/mongo-go-driver/mongo/options"
- pkgerrors "github.com/pkg/errors"
"reflect"
"strings"
"testing"
-)
-
-// Implements the mongo.Cursor interface
-type mockCursor struct {
- mongo.Cursor
- err error
- bson bson.Raw
- count int
-}
-
-func (mc *mockCursor) Next(ctx context.Context) bool {
- if mc.count > 0 {
- mc.count = mc.count - 1
- return true
- }
- return false
-}
-
-func (mc *mockCursor) DecodeBytes() (bson.Raw, error) {
- return mc.bson, mc.err
-}
-func (mc *mockCursor) Close(ctx context.Context) error {
- return nil
-}
+ pkgerrors "github.com/pkg/errors"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+)
//Implements the functions used currently in mongo.go
type mockCollection struct {
- Err error
- mCursor mongo.Cursor
+ Err error
+ mCursor *mongo.Cursor
+ mCursorCount int
}
func (c *mockCollection) InsertOne(ctx context.Context, document interface{},
@@ -89,7 +67,7 @@ func (c *mockCollection) DeleteOne(ctx context.Context, filter interface{},
}
func (c *mockCollection) Find(ctx context.Context, filter interface{},
- opts ...*options.FindOptions) (mongo.Cursor, error) {
+ opts ...*options.FindOptions) (*mongo.Cursor, error) {
return c.mCursor, c.Err
}
@@ -415,14 +393,14 @@ func TestReadAll(t *testing.T) {
"tag": "metadata",
},
mockColl: &mockCollection{
- mCursor: &mockCursor{
+ mCursor: &mongo.Cursor{
// Binary form of
// {
// "_id" : ObjectId("5c115156777ff85654248ae1"),
// "key" : "b82c4bb1-09ff-6093-4d58-8327b94e1e20",
// "metadata" : ObjectId("5c115156c9755047e318bbfd")
// }
- bson: bson.Raw{
+ Current: bson.Raw{
'\x5a', '\x00', '\x00', '\x00', '\x07', '\x5f', '\x69', '\x64',
'\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', '\xf8',
'\x56', '\x54', '\x24', '\x8a', '\xe1', '\x02', '\x6b', '\x65',
@@ -436,8 +414,8 @@ func TestReadAll(t *testing.T) {
'\x56', '\xc9', '\x75', '\x50', '\x47', '\xe3', '\x18', '\xbb',
'\xfd', '\x00',
},
- count: 1,
},
+ mCursorCount: 1,
},
expected: map[string][]byte{
"b82c4bb1-09ff-6093-4d58-8327b94e1e20": []byte{
@@ -462,14 +440,14 @@ func TestReadAll(t *testing.T) {
"tag": "tagName",
},
mockColl: &mockCollection{
- mCursor: &mockCursor{
+ mCursor: &mongo.Cursor{
// Binary form of
// {
// "_id" : ObjectId("5c115156777ff85654248ae1"),
// "key" : "b82c4bb1-09ff-6093-4d58-8327b94e1e20",
// "metadata" : ObjectId("5c115156c9755047e318bbfd")
// }
- bson: bson.Raw{
+ Current: bson.Raw{
'\x5a', '\x00', '\x00', '\x00', '\x07', '\x5f', '\x69', '\x64',
'\x00', '\x5c', '\x11', '\x51', '\x56', '\x77', '\x7f', '\xf8',
'\x56', '\x54', '\x24', '\x8a', '\xe1', '\x02', '\x6b', '\x65',
@@ -483,8 +461,8 @@ func TestReadAll(t *testing.T) {
'\x56', '\xc9', '\x75', '\x50', '\x47', '\xe3', '\x18', '\xbb',
'\xfd', '\x00',
},
- count: 1,
},
+ mCursorCount: 1,
},
expectedError: "Did not find any objects with tag",
},
@@ -508,7 +486,19 @@ func TestReadAll(t *testing.T) {
}
decodeBytes = func(sr *mongo.SingleResult) (bson.Raw, error) {
- return testCase.mockColl.mCursor.DecodeBytes()
+ return testCase.mockColl.mCursor.Current, testCase.mockColl.Err
+ }
+
+ cursorNext = func(ctx context.Context, cursor *mongo.Cursor) bool {
+ if testCase.mockColl.mCursorCount > 0 {
+ testCase.mockColl.mCursorCount -= 1
+ return true
+ }
+ return false
+ }
+
+ cursorClose = func(ctx context.Context, cursor *mongo.Cursor) error {
+ return nil
}
got, err := m.ReadAll(testCase.input["coll"].(string), testCase.input["tag"].(string))
diff --git a/src/k8splugin/internal/helm/helm.go b/src/k8splugin/internal/helm/helm.go
new file mode 100644
index 00000000..65a36d6b
--- /dev/null
+++ b/src/k8splugin/internal/helm/helm.go
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package helm
+
+import (
+ "fmt"
+ "io/ioutil"
+ "k8s.io/helm/pkg/strvals"
+ "os"
+ "path"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "github.com/ghodss/yaml"
+ pkgerrors "github.com/pkg/errors"
+
+ "k8s.io/apimachinery/pkg/util/validation"
+ "k8s.io/helm/pkg/chartutil"
+ "k8s.io/helm/pkg/manifest"
+ "k8s.io/helm/pkg/proto/hapi/chart"
+ "k8s.io/helm/pkg/releaseutil"
+ "k8s.io/helm/pkg/renderutil"
+ "k8s.io/helm/pkg/tiller"
+ "k8s.io/helm/pkg/timeconv"
+)
+
+// Template is the interface for all helm templating commands
+// Any backend implementation will implement this interface and will
+// access the functionality via this.
+type Template interface {
+ GenerateKubernetesArtifacts(
+ chartPath string,
+ valueFiles []string,
+ values []string) (map[string][]string, error)
+}
+
+// TemplateClient implements the Template interface
+// It will also be used to maintain any localized state
+type TemplateClient struct {
+ whitespaceRegex *regexp.Regexp
+ kubeVersion string
+ kubeNameSpace string
+ releaseName string
+}
+
+// NewTemplateClient returns a new instance of TemplateClient
+func NewTemplateClient(k8sversion, namespace, releasename string) *TemplateClient {
+ return &TemplateClient{
+ whitespaceRegex: regexp.MustCompile(`^\s*$`),
+ // defaultKubeVersion is the default value of --kube-version flag
+ kubeVersion: k8sversion,
+ kubeNameSpace: namespace,
+ releaseName: releasename,
+ }
+}
+
+// Combines valueFiles and values into a single values stream.
+// values takes precedence over valueFiles
+func (h *TemplateClient) processValues(valueFiles []string, values []string) ([]byte, error) {
+ base := map[string]interface{}{}
+
+ //Values files that are used for overriding the chart
+ for _, filePath := range valueFiles {
+ currentMap := map[string]interface{}{}
+
+ var bytes []byte
+ var err error
+ if strings.TrimSpace(filePath) == "-" {
+ bytes, err = ioutil.ReadAll(os.Stdin)
+ } else {
+ bytes, err = ioutil.ReadFile(filePath)
+ }
+
+ if err != nil {
+ return []byte{}, err
+ }
+
+ if err := yaml.Unmarshal(bytes, &currentMap); err != nil {
+ return []byte{}, fmt.Errorf("failed to parse %s: %s", filePath, err)
+ }
+ // Merge with the previous map
+ base = h.mergeValues(base, currentMap)
+ }
+
+ //User specified value. Similar to ones provided by -x
+ for _, value := range values {
+ if err := strvals.ParseInto(value, base); err != nil {
+ return []byte{}, fmt.Errorf("failed parsing --set data: %s", err)
+ }
+ }
+
+ return yaml.Marshal(base)
+}
+
+func (h *TemplateClient) mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
+ for k, v := range src {
+ // If the key doesn't exist already, then just set the key to that value
+ if _, exists := dest[k]; !exists {
+ dest[k] = v
+ continue
+ }
+ nextMap, ok := v.(map[string]interface{})
+ // If it isn't another map, overwrite the value
+ if !ok {
+ dest[k] = v
+ continue
+ }
+ // Edge case: If the key exists in the destination, but isn't a map
+ destMap, isMap := dest[k].(map[string]interface{})
+ // If the source map has a map for this key, prefer it
+ if !isMap {
+ dest[k] = v
+ continue
+ }
+ // If we got to this point, it is a map in both, so merge them
+ dest[k] = h.mergeValues(destMap, nextMap)
+ }
+ return dest
+}
+
+func (h *TemplateClient) ensureDirectory(f string) error {
+ base := path.Dir(f)
+ _, err := os.Stat(base)
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ return os.MkdirAll(base, 0755)
+}
+
+// GenerateKubernetesArtifacts a mapping of type to fully evaluated helm template
+func (h *TemplateClient) GenerateKubernetesArtifacts(inputPath string, valueFiles []string, values []string) (map[string][]string, error) {
+
+ var outputDir, chartPath, namespace, releaseName string
+ var retData map[string][]string
+
+ releaseName = h.releaseName
+ namespace = h.kubeNameSpace
+
+ // verify chart path exists
+ if _, err := os.Stat(inputPath); err == nil {
+ if chartPath, err = filepath.Abs(inputPath); err != nil {
+ return retData, err
+ }
+ } else {
+ return retData, err
+ }
+
+ //Create a temp directory in the system temp folder
+ outputDir, err := ioutil.TempDir("", "helm-tmpl-")
+ if err != nil {
+ return retData, pkgerrors.Wrap(err, "Got error creating temp dir")
+ }
+
+ if namespace == "" {
+ namespace = "default"
+ }
+
+ // get combined values and create config
+ rawVals, err := h.processValues(valueFiles, values)
+ if err != nil {
+ return retData, err
+ }
+ config := &chart.Config{Raw: string(rawVals), Values: map[string]*chart.Value{}}
+
+ if msgs := validation.IsDNS1123Label(releaseName); releaseName != "" && len(msgs) > 0 {
+ return retData, fmt.Errorf("release name %s is not a valid DNS label: %s", releaseName, strings.Join(msgs, ";"))
+ }
+
+ // Check chart requirements to make sure all dependencies are present in /charts
+ c, err := chartutil.Load(chartPath)
+ if err != nil {
+ return retData, pkgerrors.Errorf("Got error: %s", err.Error())
+ }
+
+ renderOpts := renderutil.Options{
+ ReleaseOptions: chartutil.ReleaseOptions{
+ Name: releaseName,
+ IsInstall: true,
+ IsUpgrade: false,
+ Time: timeconv.Now(),
+ Namespace: namespace,
+ },
+ KubeVersion: h.kubeVersion,
+ }
+
+ renderedTemplates, err := renderutil.Render(c, config, renderOpts)
+ if err != nil {
+ return retData, err
+ }
+
+ newRenderedTemplates := make(map[string]string)
+
+ //Some manifests can contain multiple yaml documents
+ //This step is splitting them up into multiple files
+ //Each file contains only a single k8s kind
+ for k, v := range renderedTemplates {
+ //Splits into manifest-0, manifest-1 etc
+ if filepath.Base(k) == "NOTES.txt" {
+ continue
+ }
+ rmap := releaseutil.SplitManifests(v)
+ count := 0
+ for _, v1 := range rmap {
+ key := fmt.Sprintf("%s-%d", k, count)
+ newRenderedTemplates[key] = v1
+ count = count + 1
+ }
+ }
+
+ listManifests := manifest.SplitManifests(newRenderedTemplates)
+ var manifestsToRender []manifest.Manifest
+ //render all manifests in the chart
+ manifestsToRender = listManifests
+ retData = make(map[string][]string)
+ for _, m := range tiller.SortByKind(manifestsToRender) {
+ data := m.Content
+ b := filepath.Base(m.Name)
+ if b == "NOTES.txt" {
+ continue
+ }
+ if strings.HasPrefix(b, "_") {
+ continue
+ }
+
+ // blank template after execution
+ if h.whitespaceRegex.MatchString(data) {
+ continue
+ }
+
+ mfilePath := filepath.Join(outputDir, m.Name)
+ h.ensureDirectory(mfilePath)
+ err = ioutil.WriteFile(mfilePath, []byte(data), 0666)
+ if err != nil {
+ return retData, err
+ }
+
+ if val, ok := retData[m.Head.Kind]; ok {
+ retData[m.Head.Kind] = append(val, mfilePath)
+ } else {
+ retData[m.Head.Kind] = []string{mfilePath}
+ }
+ }
+ return retData, nil
+}
diff --git a/src/k8splugin/internal/helm/helm_test.go b/src/k8splugin/internal/helm/helm_test.go
new file mode 100644
index 00000000..a5bcd9c8
--- /dev/null
+++ b/src/k8splugin/internal/helm/helm_test.go
@@ -0,0 +1,195 @@
+// +build unit
+
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package helm
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "io/ioutil"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestProcessValues(t *testing.T) {
+
+ chartDir := "../../mock_files/mock_charts/testchart1"
+ profileDir := "../../mock_files/mock_profiles/profile1"
+
+ testCases := []struct {
+ label string
+ valueFiles []string
+ values []string
+ expectedHash string
+ expectedError string
+ }{
+ {
+ label: "Process Values with Value Files Override",
+ valueFiles: []string{
+ filepath.Join(chartDir, "values.yaml"),
+ filepath.Join(profileDir, "override_values.yaml"),
+ },
+ //Hash of a combined values.yaml file that is expected
+ expectedHash: "c18a70f426933de3c051c996dc34fd537d0131b2d13a2112a2ecff674db6c2f9",
+ expectedError: "",
+ },
+ {
+ label: "Process Values with Values Pair Override",
+ valueFiles: []string{
+ filepath.Join(chartDir, "values.yaml"),
+ },
+ //Use the same convention as specified in helm template --set
+ values: []string{
+ "service.externalPort=82",
+ },
+ //Hash of a combined values.yaml file that is expected
+ expectedHash: "028a3521fc9f8777ea7e67a6de0c51f2c875b88ca91734999657f0ca924ddb7a",
+ expectedError: "",
+ },
+ {
+ label: "Process Values with Both Overrides",
+ valueFiles: []string{
+ filepath.Join(chartDir, "values.yaml"),
+ filepath.Join(profileDir, "override_values.yaml"),
+ },
+ //Use the same convention as specified in helm template --set
+ //Key takes precedence over the value from override_values.yaml
+ values: []string{
+ "service.externalPort=82",
+ },
+ //Hash of a combined values.yaml file that is expected
+ expectedHash: "516fab4ab7b76ba2ff35a97c2a79b74302543f532857b945f2fe25e717e755be",
+ expectedError: "",
+ },
+ }
+
+ h := sha256.New()
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ tc := NewTemplateClient("1.12.3", "testnamespace", "testreleasename")
+ out, err := tc.processValues(testCase.valueFiles, testCase.values)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Got an error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Got unexpected error message %s", err)
+ }
+ } else {
+ //Compute the hash of returned data and compare
+ h.Write(out)
+ gotHash := fmt.Sprintf("%x", h.Sum(nil))
+ h.Reset()
+ if gotHash != testCase.expectedHash {
+ t.Fatalf("Got unexpected values.yaml %s", out)
+ }
+ }
+ })
+ }
+}
+
+func TestGenerateKubernetesArtifacts(t *testing.T) {
+
+ chartDir := "../../mock_files/mock_charts/testchart1"
+ profileDir := "../../mock_files/mock_profiles/profile1"
+
+ testCases := []struct {
+ label string
+ chartPath string
+ valueFiles []string
+ values []string
+ expectedHashMap map[string]string
+ expectedError string
+ }{
+ {
+ label: "Generate artifacts without any overrides",
+ chartPath: chartDir,
+ valueFiles: []string{},
+ values: []string{},
+ //sha256 hash of the evaluated templates in each chart
+ expectedHashMap: map[string]string{
+ "testchart1/templates/service.yaml": "bbd7257d1f6ab958680e642a8fbbbea2002ebbaa9276fb51fbd71b4b66a772cc",
+ "subcharta/templates/service.yaml": "570389588fffdb7193ab265888d781f3d751f3a40362533344f9aa7bb93a8bb0",
+ "subchartb/templates/service.yaml": "5654e03d922e8ec49649b4bbda9dfc9e643b3b7c9c18b602cc7e26fd36a39c2a",
+ },
+ expectedError: "",
+ },
+ {
+ label: "Generate artifacts with overrides",
+ chartPath: chartDir,
+ valueFiles: []string{
+ filepath.Join(profileDir, "override_values.yaml"),
+ },
+ values: []string{
+ "service.externalPort=82",
+ },
+ //sha256 hash of the evaluated templates in each chart
+ expectedHashMap: map[string]string{
+ "testchart1/templates/service.yaml": "4c5aa5d38b763fe4730fc31a759c40566a99a9c51f5e0fc7f93473c9affc2ca8",
+ "subcharta/templates/service.yaml": "570389588fffdb7193ab265888d781f3d751f3a40362533344f9aa7bb93a8bb0",
+ "subchartb/templates/service.yaml": "5654e03d922e8ec49649b4bbda9dfc9e643b3b7c9c18b602cc7e26fd36a39c2a",
+ },
+ expectedError: "",
+ },
+ }
+
+ h := sha256.New()
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ tc := NewTemplateClient("1.12.3", "testnamespace", "testreleasename")
+ out, err := tc.GenerateKubernetesArtifacts(testCase.chartPath, testCase.valueFiles,
+ testCase.values)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Got an error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Got unexpected error message %s", err)
+ }
+ } else {
+ //Compute the hash of returned data and compare
+ for _, v := range out {
+ for _, f := range v {
+ data, err := ioutil.ReadFile(f)
+ if err != nil {
+ t.Errorf("Unable to read file %s", v)
+ }
+ h.Write(data)
+ gotHash := fmt.Sprintf("%x", h.Sum(nil))
+ h.Reset()
+
+ //Find the right hash from expectedHashMap
+ expectedHash := ""
+ for k1, v1 := range testCase.expectedHashMap {
+ if strings.Contains(f, k1) == true {
+ expectedHash = v1
+ break
+ }
+ }
+ if gotHash != expectedHash {
+ t.Fatalf("Got unexpected hash for %s", f)
+ }
+ }
+ }
+ }
+ })
+ }
+}
diff --git a/src/k8splugin/internal/rb/definition.go b/src/k8splugin/internal/rb/definition.go
index 19844990..4eaa9578 100644
--- a/src/k8splugin/internal/rb/definition.go
+++ b/src/k8splugin/internal/rb/definition.go
@@ -19,8 +19,12 @@ package rb
import (
"bytes"
"encoding/base64"
- "k8splugin/internal/db"
+ "io/ioutil"
"log"
+ "os"
+ "path/filepath"
+
+ "k8splugin/internal/db"
uuid "github.com/hashicorp/go-uuid"
pkgerrors "github.com/pkg/errors"
@@ -142,8 +146,8 @@ func (v *DefinitionClient) Delete(id string) error {
// Upload the contents of resource bundle into database
func (v *DefinitionClient) Upload(id string, inp []byte) error {
- //ignore the returned data here
- _, err := v.Get(id)
+ //Check if definition metadata exists
+ def, err := v.Get(id)
if err != nil {
return pkgerrors.Errorf("Invalid Definition ID provided: %s", err.Error())
}
@@ -153,6 +157,39 @@ func (v *DefinitionClient) Upload(id string, inp []byte) error {
return pkgerrors.Errorf("Error in file format: %s", err.Error())
}
+ //Detect chart name from data if it was not provided originally
+ if def.ChartName == "" {
+ path, err := ExtractTarBall(bytes.NewBuffer(inp))
+ if err != nil {
+ return pkgerrors.Wrap(err, "Detecting chart name")
+ }
+
+ finfo, err := ioutil.ReadDir(path)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Detecting chart name")
+ }
+
+ //Store the first directory with Chart.yaml found as the chart name
+ for _, f := range finfo {
+ if f.IsDir() {
+ //Check if Chart.yaml exists
+ if _, err = os.Stat(filepath.Join(path, f.Name(), "Chart.yaml")); err == nil {
+ def.ChartName = f.Name()
+ break
+ }
+ }
+ }
+
+ if def.ChartName == "" {
+ return pkgerrors.New("Unable to detect chart name")
+ }
+
+ _, err = v.Create(def)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Storing updated chart metadata")
+ }
+ }
+
//Encode given byte stream to text for storage
encodedStr := base64.StdEncoding.EncodeToString(inp)
err = db.DBconn.Create(v.storeName, id, v.tagContent, encodedStr)
diff --git a/src/k8splugin/internal/rb/definition_test.go b/src/k8splugin/internal/rb/definition_test.go
index f720b6a9..b1875fd7 100644
--- a/src/k8splugin/internal/rb/definition_test.go
+++ b/src/k8splugin/internal/rb/definition_test.go
@@ -281,8 +281,95 @@ func TestUploadDefinition(t *testing.T) {
mockdb *db.MockDB
}{
{
- label: "Upload Resource Bundle Definition",
+ label: "Upload With Chart Name Detection",
inp: "123e4567-e89b-12d3-a456-426655440000",
+ //Binary format for testchart/Chart.yaml
+ content: []byte{
+ 0x1f, 0x8b, 0x08, 0x08, 0xb3, 0xeb, 0x86, 0x5c,
+ 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x63, 0x68,
+ 0x61, 0x72, 0x74, 0x2e, 0x74, 0x61, 0x72, 0x00,
+ 0xed, 0xd2, 0x41, 0x4b, 0xc3, 0x30, 0x18, 0xc6,
+ 0xf1, 0x9c, 0xfb, 0x29, 0xde, 0x4f, 0x50, 0x93,
+ 0x36, 0x69, 0x60, 0x37, 0xd9, 0x45, 0xf0, 0xee,
+ 0x55, 0xe2, 0x16, 0xb1, 0x74, 0xed, 0x46, 0x9a,
+ 0x0d, 0xfc, 0xf6, 0xae, 0x83, 0x4d, 0x91, 0x89,
+ 0x97, 0x0d, 0x91, 0xfd, 0x7f, 0x87, 0x84, 0x90,
+ 0x90, 0xbc, 0xe1, 0x79, 0x73, 0x1c, 0xf3, 0xe2,
+ 0x2d, 0xa4, 0x7c, 0xa7, 0xae, 0x46, 0xef, 0x79,
+ 0xef, 0xa6, 0xd9, 0x78, 0xa7, 0xbf, 0xce, 0x47,
+ 0xca, 0xd4, 0xd6, 0x1a, 0xd7, 0xb8, 0xa6, 0xb6,
+ 0x4a, 0x1b, 0x5b, 0xbb, 0x4a, 0x89, 0xbb, 0x5e,
+ 0x49, 0x9f, 0xb6, 0x63, 0x0e, 0x49, 0x44, 0x85,
+ 0xe5, 0x73, 0xd7, 0x75, 0xa1, 0x6f, 0x87, 0x78,
+ 0xf6, 0xdc, 0x6f, 0xfb, 0xff, 0x54, 0x3e, 0xe5,
+ 0x3f, 0x9f, 0xc6, 0xf2, 0x3d, 0xf4, 0xab, 0x4b,
+ 0xbf, 0x31, 0x05, 0xdc, 0x34, 0xf6, 0xc7, 0xfc,
+ 0x4d, 0xe5, 0xbf, 0xe5, 0xdf, 0x54, 0xde, 0x2b,
+ 0xd1, 0x97, 0x2e, 0xe4, 0x9c, 0x1b, 0xcf, 0x3f,
+ 0x6c, 0xda, 0xa7, 0x98, 0xc6, 0x76, 0x3d, 0xcc,
+ 0x64, 0x67, 0x8a, 0x65, 0x1c, 0x17, 0xa9, 0xdd,
+ 0xe4, 0xc3, 0xfa, 0x5e, 0x1e, 0xe2, 0xaa, 0x97,
+ 0x43, 0x7b, 0xc8, 0xeb, 0x3a, 0xc9, 0xe3, 0xf6,
+ 0x25, 0xa6, 0x21, 0xee, 0x7b, 0xa6, 0x18, 0x42,
+ 0x1f, 0x67, 0x72, 0xea, 0x9e, 0x62, 0x77, 0xbc,
+ 0x44, 0x97, 0xa6, 0xd4, 0xc5, 0x5f, 0x7f, 0x0b,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xb8, 0x09, 0x1f, 0xae,
+ 0x48, 0xfe, 0xe8, 0x00, 0x28, 0x00, 0x00,
+ },
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ "123e4567-e89b-12d3-a456-426655440000": {
+ "metadata": []byte(
+ "{\"name\":\"testresourcebundle\"," +
+ "\"description\":\"testresourcebundle\"," +
+ "\"uuid\":\"123e4567-e89b-12d3-a456-426655440000\"," +
+ "\"service-type\":\"firewall\"}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Upload With Chart Name",
+ inp: "123e4567-e89b-12d3-a456-426655440000",
+ content: []byte{
+ 0x1f, 0x8b, 0x08, 0x08, 0xb0, 0x6b, 0xf4, 0x5b,
+ 0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74,
+ 0x61, 0x72, 0x00, 0xed, 0xce, 0x41, 0x0a, 0xc2,
+ 0x30, 0x10, 0x85, 0xe1, 0xac, 0x3d, 0x45, 0x4e,
+ 0x50, 0x12, 0xd2, 0xc4, 0xe3, 0x48, 0xa0, 0x01,
+ 0x4b, 0x52, 0x0b, 0xed, 0x88, 0x1e, 0xdf, 0x48,
+ 0x11, 0x5c, 0x08, 0xa5, 0x8b, 0x52, 0x84, 0xff,
+ 0xdb, 0xbc, 0x61, 0x66, 0x16, 0x4f, 0xd2, 0x2c,
+ 0x8d, 0x3c, 0x45, 0xed, 0xc8, 0x54, 0x21, 0xb4,
+ 0xef, 0xb4, 0x67, 0x6f, 0xbe, 0x73, 0x61, 0x9d,
+ 0xb2, 0xce, 0xd5, 0x55, 0xf0, 0xde, 0xd7, 0x3f,
+ 0xdb, 0xd6, 0x49, 0x69, 0xb3, 0x67, 0xa9, 0x8f,
+ 0xfb, 0x2c, 0x71, 0xd2, 0x5a, 0xc5, 0xee, 0x92,
+ 0x73, 0x8e, 0x43, 0x7f, 0x4b, 0x3f, 0xff, 0xd6,
+ 0xee, 0x7f, 0xea, 0x9a, 0x4a, 0x19, 0x1f, 0xe3,
+ 0x54, 0xba, 0xd3, 0xd1, 0x55, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x1b, 0xbc, 0x00, 0xb5, 0xe8,
+ 0x4a, 0xf9, 0x00, 0x28, 0x00, 0x00,
+ },
+ mockdb: &db.MockDB{
+ Items: map[string]map[string][]byte{
+ "123e4567-e89b-12d3-a456-426655440000": {
+ "metadata": []byte(
+ "{\"name\":\"testresourcebundle\"," +
+ "\"description\":\"testresourcebundle\"," +
+ "\"chart-name\":\"testchart\"," +
+ "\"uuid\":\"123e4567-e89b-12d3-a456-426655440000\"," +
+ "\"service-type\":\"firewall\"}"),
+ },
+ },
+ },
+ },
+ {
+ label: "Upload Without Chart.yaml",
+ inp: "123e4567-e89b-12d3-a456-426655440000",
+ expectedError: "Unable to detect chart name",
content: []byte{
0x1f, 0x8b, 0x08, 0x08, 0xb0, 0x6b, 0xf4, 0x5b,
0x00, 0x03, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74,
diff --git a/src/k8splugin/internal/rb/profile.go b/src/k8splugin/internal/rb/profile.go
index 2456ad2d..d78e32e4 100644
--- a/src/k8splugin/internal/rb/profile.go
+++ b/src/k8splugin/internal/rb/profile.go
@@ -51,6 +51,7 @@ type ProfileManager interface {
type ProfileClient struct {
storeName string
tagMeta, tagContent string
+ manifestName string
}
// NewProfileClient returns an instance of the ProfileClient
@@ -58,9 +59,10 @@ type ProfileClient struct {
// Uses rb/def prefix
func NewProfileClient() *ProfileClient {
return &ProfileClient{
- storeName: "rbprofile",
- tagMeta: "metadata",
- tagContent: "content",
+ storeName: "rbprofile",
+ tagMeta: "metadata",
+ tagContent: "content",
+ manifestName: "manifest.yaml",
}
}
diff --git a/src/k8splugin/internal/rb/profile_yaml.go b/src/k8splugin/internal/rb/profile_yaml.go
new file mode 100644
index 00000000..ba55fba9
--- /dev/null
+++ b/src/k8splugin/internal/rb/profile_yaml.go
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package rb
+
+import (
+ "io/ioutil"
+ "log"
+ "path/filepath"
+
+ "github.com/ghodss/yaml"
+ pkgerrors "github.com/pkg/errors"
+)
+
+/*
+#Sample Yaml format for profile manifest.yaml
+---
+version: v1
+type:
+ values: "values_override.yaml"
+ configresource:
+ - filepath: config.yaml
+ chartpath: chart/config/resources/config.yaml
+ - filepath: config2.yaml
+ chartpath: chart/config/resources/config2.yaml
+*/
+
+type overrideFiles struct {
+ FilePath string `yaml:"filepath"`
+ ChartPath string `yaml:"chartpath"`
+}
+
+type supportedOverrides struct {
+ ConfigResource []overrideFiles `yaml:"configresource"`
+ Values string `yaml:"values"`
+}
+
+type profileOverride struct {
+ Version string `yaml:"version"`
+ Type supportedOverrides `yaml:"type"`
+}
+
+type ProfileYamlClient struct {
+ path string
+ override profileOverride
+}
+
+func (p ProfileYamlClient) Print() {
+ log.Println(p.override)
+}
+
+//GetValues returns a path to the override values.yam
+//that was part of the profile
+func (p ProfileYamlClient) GetValues() string {
+ return filepath.Join(p.path, p.override.Type.Values)
+}
+
+//CopyConfigurationOverrides copies the various files that are
+//provided as overrides to their corresponding locations within
+//the destination chart.
+func (p ProfileYamlClient) CopyConfigurationOverrides(chartPath string) error {
+
+ //Iterate over each configresource and copy that file into
+ //the respective path in the chart.
+ for _, v := range p.override.Type.ConfigResource {
+ data, err := ioutil.ReadFile(filepath.Join(p.path, v.FilePath))
+ if err != nil {
+ return pkgerrors.Wrap(err, "Reading configuration file")
+ }
+ err = ioutil.WriteFile(filepath.Join(chartPath, v.ChartPath), data, 0644)
+ if err != nil {
+ return pkgerrors.Wrap(err, "Writing configuration file into chartpath")
+ }
+ }
+
+ return nil
+}
+
+//ProcessProfileYaml parses the manifest.yaml file that is part of the profile
+//package and creates the appropriate structures out of it.
+func ProcessProfileYaml(fpath string, manifestFileName string) (ProfileYamlClient, error) {
+
+ p := filepath.Join(fpath, manifestFileName)
+ data, err := ioutil.ReadFile(p)
+ if err != nil {
+ return ProfileYamlClient{}, pkgerrors.Wrap(err, "Reading manifest file")
+ }
+
+ out := profileOverride{}
+ err = yaml.Unmarshal(data, &out)
+ if err != nil {
+ return ProfileYamlClient{}, pkgerrors.Wrap(err, "Marshaling manifest yaml file")
+ }
+
+ return ProfileYamlClient{path: fpath, override: out}, nil
+}
diff --git a/src/k8splugin/internal/rb/profile_yaml_test.go b/src/k8splugin/internal/rb/profile_yaml_test.go
new file mode 100644
index 00000000..c29bf9ed
--- /dev/null
+++ b/src/k8splugin/internal/rb/profile_yaml_test.go
@@ -0,0 +1,139 @@
+// +build unit
+
+/*
+ * Copyright 2018 Intel Corporation, Inc
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package rb
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestProcessProfileYaml(t *testing.T) {
+
+ profileDir := "../../mock_files/mock_profiles/profile1"
+ manifestFile := "manifest.yaml"
+ faultymanifestfile := "faulty-manifest.yaml"
+
+ testCases := []struct {
+ label string
+ prDir, manifest string
+ expectedError string
+ }{
+ {
+ label: "Process Profile Yaml",
+ prDir: profileDir,
+ manifest: manifestFile,
+ expectedError: "",
+ },
+ {
+ label: "Non existent manifest file",
+ prDir: profileDir,
+ manifest: "non-existant-file.yaml",
+ expectedError: "Reading manifest file",
+ },
+ {
+ label: "Faulty manifest file",
+ prDir: profileDir,
+ manifest: faultymanifestfile,
+ expectedError: "Marshaling manifest yaml file",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ _, err := ProcessProfileYaml(testCase.prDir, testCase.manifest)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Got an error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Got unexpected error message %s", err)
+ }
+ }
+ })
+ }
+}
+
+func TestCopyConfigurationOverrides(t *testing.T) {
+
+ profileDir := "../../mock_files/mock_profiles/profile1"
+ profileFileName := "p1.yaml"
+ manifestFile := "manifest.yaml"
+ faultySrcManifestFile := "faulty-src-manifest.yaml"
+ faultyDestManifestFile := "faulty-dest-manifest.yaml"
+ chartBaseDir := "../../mock_files/mock_charts"
+
+ //Remove the testchart1/templates/p1.yaml file that gets copied over
+ defer os.Remove(filepath.Join(chartBaseDir, "testchart1", "templates", profileFileName))
+
+ testCases := []struct {
+ label string
+ prDir, chDir, manifest string
+ expectedError string
+ }{
+ {
+ label: "Copy Configuration Overrides",
+ prDir: profileDir,
+ manifest: manifestFile,
+ chDir: chartBaseDir,
+ expectedError: "",
+ },
+ {
+ label: "Copy Configuration Overrides Faulty Source",
+ prDir: profileDir,
+ manifest: faultySrcManifestFile,
+ chDir: chartBaseDir,
+ expectedError: "Reading configuration file",
+ },
+ {
+ label: "Copy Configuration Overrides Faulty Destination",
+ prDir: profileDir,
+ manifest: faultyDestManifestFile,
+ chDir: chartBaseDir,
+ expectedError: "Writing configuration file",
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.label, func(t *testing.T) {
+ p, err := ProcessProfileYaml(testCase.prDir, testCase.manifest)
+ if err != nil {
+ t.Fatalf("Got unexpected error processing yaml %s", err)
+ }
+
+ err = p.CopyConfigurationOverrides(testCase.chDir)
+ if err != nil {
+ if testCase.expectedError == "" {
+ t.Fatalf("Got error %s", err)
+ }
+ if strings.Contains(err.Error(), testCase.expectedError) == false {
+ t.Fatalf("Got unexpected error message %s", err)
+ }
+
+ } else {
+ //Check if the file got copied over
+ if _, err = os.Stat(filepath.Join(testCase.chDir, "testchart1",
+ "templates", profileFileName)); os.IsNotExist(err) {
+ t.Fatalf("Failed to copy override file: %s", profileFileName)
+ }
+ }
+ })
+ }
+}