diff options
Diffstat (limited to 'src/k8splugin/internal')
-rw-r--r-- | src/k8splugin/internal/db/mongo.go | 38 | ||||
-rw-r--r-- | src/k8splugin/internal/db/mongo_test.go | 66 | ||||
-rw-r--r-- | src/k8splugin/internal/helm/helm.go | 259 | ||||
-rw-r--r-- | src/k8splugin/internal/helm/helm_test.go | 195 | ||||
-rw-r--r-- | src/k8splugin/internal/rb/definition.go | 43 | ||||
-rw-r--r-- | src/k8splugin/internal/rb/definition_test.go | 89 | ||||
-rw-r--r-- | src/k8splugin/internal/rb/profile.go | 8 | ||||
-rw-r--r-- | src/k8splugin/internal/rb/profile_yaml.go | 109 | ||||
-rw-r--r-- | src/k8splugin/internal/rb/profile_yaml_test.go | 139 |
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, ¤tMap); 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) + } + } + }) + } +} |