From 4b8a74c996457c39518aa5284bcbb76ba3b6b29f Mon Sep 17 00:00:00 2001 From: GDW1 Date: Thu, 29 Jun 2023 16:03:16 -0700 Subject: [PATCH 01/11] Filter and Print options for queries. example shown with fsoc solution list --- cmd/solution/list.go | 7 ++++--- cmdkit/fetch_and_print.go | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/cmd/solution/list.go b/cmd/solution/list.go index f49f46cf..265134b5 100644 --- a/cmd/solution/list.go +++ b/cmd/solution/list.go @@ -71,13 +71,14 @@ func getSolutionList(cmd *cobra.Command, args []string) { // get data and display solutionBaseURL := getSolutionListUrl() + var filters []string if subscribed { - solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed eq true") + filters = []string{"filter=" + url.QueryEscape("data.isSubscribed eq true")} } else if unsubscribed { - solutionBaseURL += "?filter=" + url.QueryEscape("data.isSubscribed ne true") + filters = []string{"filter=" + url.QueryEscape("data.isSubscribed ne true")} } println(solutionBaseURL) - cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true}) + cmdkit.FetchAndPrint(cmd, solutionBaseURL, &cmdkit.FetchAndPrintOptions{Headers: headers, IsCollection: true, Filters: filters}) } func getSolutionListUrl() string { diff --git a/cmdkit/fetch_and_print.go b/cmdkit/fetch_and_print.go index 82cb88f2..36dcc78b 100644 --- a/cmdkit/fetch_and_print.go +++ b/cmdkit/fetch_and_print.go @@ -16,6 +16,7 @@ package cmdkit import ( "reflect" + "strings" "github.com/apex/log" "github.com/spf13/cobra" @@ -30,6 +31,7 @@ type FetchAndPrintOptions struct { Body any // body to send with the request (nil for no body) ResponseType *reflect.Type // structure type to parse response into (for schema validation & fields) (nil for none) IsCollection bool // set to true for GET to request a collection that may be paginated (see platform/api/collection.go) + Filters []string } // FetchAndPrint consolidates the common sequence of fetching from the server and @@ -61,6 +63,19 @@ func FetchAndPrint(cmd *cobra.Command, path string, options *FetchAndPrintOption // fetch data var err error + + if options.Filters != nil { + // If there are filters, apply them to query path + numberOfFilters := len(strings.Split(path, "?")) + if numberOfFilters != 1 && numberOfFilters != 0 { + // Case 1: There is already a query in path append to the path + path += "&" + strings.Join(options.Filters, "&") + } else { + // Case 2: There is no query in path + path += "?" + strings.Join(options.Filters, "&") + } + } + if options != nil && options.IsCollection { if method != "GET" { log.Fatalf("bug: cannot request %q for a collection at %q, only GET is supported for collections", method, path) From e38c5e0f85df4f226bd396c6235b8b7bfacecd2d Mon Sep 17 00:00:00 2001 From: Renato Quedas <66440106+rquedas@users.noreply.github.com> Date: Mon, 3 Jul 2023 01:36:12 -0300 Subject: [PATCH 02/11] melt model support for isolation. (#133) * melt model support for isolation. * fixing make pre-commit issue --- cmd/melt/model.go | 103 +++++++++++++++++++++++++++++++--- cmd/solution/extend-dashui.go | 2 +- cmd/solution/extend-fmm.go | 6 +- cmd/solution/extend.go | 2 +- cmd/solution/fork.go | 6 +- cmd/solution/isolate.go | 8 +-- cmd/solution/types.go | 23 +++++++- 7 files changed, 129 insertions(+), 21 deletions(-) diff --git a/cmd/melt/model.go b/cmd/melt/model.go index 292b504d..67127d62 100644 --- a/cmd/melt/model.go +++ b/cmd/melt/model.go @@ -3,9 +3,11 @@ package melt import ( "fmt" "os" + "path/filepath" "strings" "github.com/apex/log" + "github.com/spf13/afero" "github.com/spf13/cobra" "golang.org/x/exp/maps" "gopkg.in/yaml.v2" @@ -25,36 +27,123 @@ var meltModelCmd = &cobra.Command{ } func init() { + meltModelCmd.Flags().String("tag", "", "tag for the solution") + meltModelCmd.Flags().String("env-file", "./env.json", "path to the env vars json file") + + meltModelCmd.MarkFlagsMutuallyExclusive("tag", "env-file") + meltCmd.AddCommand(meltModelCmd) } func meltModel(cmd *cobra.Command, args []string) { manifest := sol.GetManifest() - fileName := fmt.Sprintf("%s-%s-melt.yaml", manifest.Name, manifest.SolutionVersion) - fsocData := getFsocDataModel(cmd, manifest) - output.PrintCmdStatus(cmd, fmt.Sprintf("Generating %s\n", fileName)) - writeDataFile(fsocData, fileName) + if manifest.HasIsolation() { + + if !(cmd.Flags().Changed("tag") || cmd.Flags().Changed("env-file")) { + log.Fatal("One of the required tags (--tag or --env-file) required for isolation support is missing!") + } + + tag, _ := cmd.Flags().GetString("tag") + envVarsFile, _ := cmd.Flags().GetString("env-file") + if tag != "" { + envVarsFile = "" // remove default when tag is specified + } + + envVars, err := sol.LoadEnvVars(cmd, tag, envVarsFile) + if err != nil { + log.Fatalf("Failed to define isolation environment: %v", err) + } + + currentDirectory, err := filepath.Abs(".") + if err != nil { + log.Fatalf("Error getting current directory: %v", err) + } + + fileSystemRoot := afero.NewBasePathFs(afero.NewOsFs(), currentDirectory) + + isolateNamespace := fmt.Sprintf("%s%s", strings.Split(manifest.Name, "$")[0], sol.GetTag(envVars)) + fileName := fmt.Sprintf("%s-%s-melt.yaml", isolateNamespace, manifest.SolutionVersion) + + fsocData := getFsocDataModel(cmd, manifest, isolateNamespace) + output.PrintCmdStatus(cmd, fmt.Sprintf("Generating %s\n", fileName)) + writeDataFile(fsocData, fileName) + + err = sol.ReplaceStringInFile(fileSystemRoot, fileName, "${sys.solutionId}", isolateNamespace) + if err != nil { + log.Fatalf("Error isolating melt model file: %v", err) + } + } else { + fileName := fmt.Sprintf("%s-%s-melt.yaml", manifest.Name, manifest.SolutionVersion) + fsocData := getFsocDataModel(cmd, manifest, "") + output.PrintCmdStatus(cmd, fmt.Sprintf("Generating %s\n", fileName)) + writeDataFile(fsocData, fileName) + } + } -func getFsocDataModel(cmd *cobra.Command, manifest *sol.Manifest) *melt.FsocData { +func getFsocDataModel(cmd *cobra.Command, manifest *sol.Manifest, isolationNamespace string) *melt.FsocData { fsocData := &melt.FsocData{} fmmEntities := manifest.GetFmmEntities() output.PrintCmdStatus(cmd, fmt.Sprintf("Adding %v entities to the fsoc data model\n", len(fmmEntities))) fmmMetrics := manifest.GetFmmMetrics() output.PrintCmdStatus(cmd, fmt.Sprintf("Adding %v metrics to the fsoc data model\n", len(fmmMetrics))) - fsocMetrics := GetFsocMetrics(fmmMetrics) fmmEvents := manifest.GetFmmEvents() output.PrintCmdStatus(cmd, fmt.Sprintf("Adding %v events to the fsoc data model\n", len(fmmEvents))) - fsocEvents := GetFsocEvents(fmmEvents) + if isolationNamespace != "" { + realNamespace := manifest.GetSolutionName() + + for _, e := range fmmEntities { + e.Namespace.Name = isolationNamespace + + fmmAttrsDefs := e.AttributeDefinitions.Attributes + for k, v := range fmmAttrsDefs { + if strings.Contains(k, realNamespace) { + entityAttr := k[len(realNamespace)+1:] + newKey := fmt.Sprintf("%s.%s", e.Namespace.Name, entityAttr) + newValue := v + delete(fmmAttrsDefs, k) + fmmAttrsDefs[newKey] = newValue + } + } + + metricRefs := e.MetricTypes + e.MetricTypes = GetIsolatedRefs(metricRefs, manifest, isolationNamespace) + + evtRefs := e.EventTypes + e.EventTypes = GetIsolatedRefs(evtRefs, manifest, isolationNamespace) + + } + for _, m := range fmmMetrics { + m.Namespace.Name = isolationNamespace + } + for _, evt := range fmmEvents { + evt.Namespace.Name = isolationNamespace + } + } + + fsocMetrics := GetFsocMetrics(fmmMetrics) + fsocEvents := GetFsocEvents(fmmEvents) fsocEntities := GetFsocEntities(fmmEntities, fsocMetrics, fsocEvents) fsocData.Melt = fsocEntities return fsocData } +func GetIsolatedRefs(fmmTypeRefs []string, manifest *sol.Manifest, isolationNamespace string) []string { + newFmmTypeRefs := make([]string, 0) + for _, typeRef := range fmmTypeRefs { + fmmTypeConvention := strings.Split(typeRef, ":") + if strings.Contains(fmmTypeConvention[0], manifest.GetNamespaceName()) { + isolateMetricRef := fmt.Sprintf("%s:%s", isolationNamespace, fmmTypeConvention[1]) + newFmmTypeRefs = append(newFmmTypeRefs, isolateMetricRef) + } + } + return newFmmTypeRefs +} + func GetFsocEvents(fmmEvents []*sol.FmmEvent) []*melt.Log { fsocEvents := make([]*melt.Log, 0) diff --git a/cmd/solution/extend-dashui.go b/cmd/solution/extend-dashui.go index d296e35a..e19dd161 100644 --- a/cmd/solution/extend-dashui.go +++ b/cmd/solution/extend-dashui.go @@ -66,7 +66,7 @@ func getEcpDetails(entity *FmmEntity) *DashuiTemplate { } func getEcpHome(manifest *Manifest) *DashuiTemplatePropsExtension { - namespaceName := manifest.getNamespaceName() + namespaceName := manifest.GetNamespaceName() id := fmt.Sprintf("%s:%sEcpHomeExtension", namespaceName, namespaceName) name := "dashui:ecpHome" view := "default" diff --git a/cmd/solution/extend-fmm.go b/cmd/solution/extend-fmm.go index 4fe069d6..989e98e7 100644 --- a/cmd/solution/extend-fmm.go +++ b/cmd/solution/extend-fmm.go @@ -27,7 +27,7 @@ import ( func getResourceMap(cmd *cobra.Command, entityName string, manifest *Manifest) *FmmResourceMapping { var newResoureMapping *FmmResourceMapping - namespaceName := manifest.getNamespaceName() + namespaceName := manifest.GetNamespaceName() entity := findEntity(entityName, manifest) name := fmt.Sprintf("%s_%s_entity_mapping", namespaceName, entityName) entityType := fmt.Sprintf("%s:%s", namespaceName, entityName) @@ -268,8 +268,8 @@ func getServiceComponent(serviceName string) *ServiceDef { func checkCreateSolutionNamespace(cmd *cobra.Command, manifest *Manifest, folderName string) { componentType := "fmm:namespace" - namespaceName := manifest.getNamespaceName() - fileName := namespaceName + ".json" + namespaceName := manifest.GetNamespaceName() + fileName := manifest.GetSolutionName() + ".json" objFilePath := fmt.Sprintf("%s/%s", folderName, fileName) componentDef := manifest.GetComponentDef(componentType) diff --git a/cmd/solution/extend.go b/cmd/solution/extend.go index e037624c..7af64079 100644 --- a/cmd/solution/extend.go +++ b/cmd/solution/extend.go @@ -184,7 +184,7 @@ func addNewComponent(cmd *cobra.Command, manifest *Manifest, folderName, compone var namespaceName string if strings.Contains(componentType, "fmm") { checkCreateSolutionNamespace(cmd, manifest, "objects/model/namespaces") - namespaceName = manifest.getNamespaceName() + namespaceName = manifest.GetNamespaceName() } switch componentType { diff --git a/cmd/solution/fork.go b/cmd/solution/fork.go index 870403c7..cdc0d2a2 100644 --- a/cmd/solution/fork.go +++ b/cmd/solution/fork.go @@ -256,7 +256,7 @@ func refactorSolution(fileSystem afero.Fs, manifest *Manifest, forkName string) var err error for _, objDef := range objDefs { if objDef.ObjectsFile != "" { - err = replaceStringInFile(fileSystem, objDef.ObjectsFile, manifest.Name, forkName) + err = ReplaceStringInFile(fileSystem, objDef.ObjectsFile, manifest.Name, forkName) } else { wkDir, _ := os.Getwd() dirPath := fmt.Sprintf("%s/%s/%s", wkDir, forkName, objDef.ObjectsDir) @@ -268,7 +268,7 @@ func refactorSolution(fileSystem afero.Fs, manifest *Manifest, forkName string) if !info.IsDir() { removeStr := fmt.Sprintf("%s/%s/", wkDir, forkName) filePath := strings.ReplaceAll(path, removeStr, "") - err = replaceStringInFile(fileSystem, filePath, manifest.Name, forkName) + err = ReplaceStringInFile(fileSystem, filePath, manifest.Name, forkName) } return err }) @@ -277,7 +277,7 @@ func refactorSolution(fileSystem afero.Fs, manifest *Manifest, forkName string) return err } -func replaceStringInFile(fileSystem afero.Fs, filePath string, searchValue string, replaceValue string) error { +func ReplaceStringInFile(fileSystem afero.Fs, filePath string, searchValue string, replaceValue string) error { data, err := afero.ReadFile(fileSystem, filePath) if err != nil { return err diff --git a/cmd/solution/isolate.go b/cmd/solution/isolate.go index e243358d..18bf3550 100644 --- a/cmd/solution/isolate.go +++ b/cmd/solution/isolate.go @@ -128,7 +128,7 @@ func isolateSolution(cmd *cobra.Command, srcFolder, targetFolder, targetFile, ta } // parse env vars - envVars, err := loadEnvVars(cmd, tag, envVarsFile) + envVars, err := LoadEnvVars(cmd, tag, envVarsFile) if err != nil { return "", "", err } @@ -186,7 +186,7 @@ func isolateSolution(cmd *cobra.Command, srcFolder, targetFolder, targetFile, ta log.Info("Pseudo-isolation successfully completed") - return mf.Name, getTag(envVars), nil + return mf.Name, GetTag(envVars), nil } func prepareForIsolation(srcPath, targetPath, targetFile string, envVars interface{}) error { @@ -287,7 +287,7 @@ func traverseSolutionFolder(dirPath string, mf *Manifest, srcPath, targetPath st return err } -func loadEnvVars(cmd *cobra.Command, tag, envVarsFile string) (interface{}, error) { +func LoadEnvVars(cmd *cobra.Command, tag, envVarsFile string) (interface{}, error) { // create ad-hoc env vars if the tag flag is specified (instead of an env json file) if tag != "" { envVars := map[string]interface{}{ @@ -317,7 +317,7 @@ func loadEnvVars(cmd *cobra.Command, tag, envVarsFile string) (interface{}, erro return envVars, nil } -func getTag(envVars interface{}) string { +func GetTag(envVars interface{}) string { if root, ok := envVars.(map[string]any); ok { if env, ok := root["env"].(map[string]any); ok { if tag, ok := env["tag"].(string); ok && tag != "" { diff --git a/cmd/solution/types.go b/cmd/solution/types.go index 1c283cda..4ad19ba0 100644 --- a/cmd/solution/types.go +++ b/cmd/solution/types.go @@ -16,6 +16,7 @@ package solution import ( "encoding/json" + "fmt" "io" "os" "path/filepath" @@ -85,14 +86,26 @@ type SolutionList struct { Items []Solution `json:"items"` } -func (manifest *Manifest) getNamespaceName() string { +func (manifest *Manifest) GetNamespaceName() string { namespaceName := manifest.Name - if strings.Contains(manifest.Name, "${") { + if manifest.HasIsolation() { namespaceName = "${sys.solutionId}" } return namespaceName } +func (manifest *Manifest) GetSolutionName() string { + solutionName := manifest.Name + if manifest.HasIsolation() { + solutionName = strings.Split(manifest.Name, "${")[0] + } + return solutionName +} + +func (manifest *Manifest) HasIsolation() bool { + return strings.Contains(manifest.Name, "${") +} + func (manifest *Manifest) GetFmmEntities() []*FmmEntity { fmmEntities := make([]*FmmEntity, 0) entityComponentDefs := manifest.GetComponentDefs("fmm:entity") @@ -210,6 +223,12 @@ func (manifest *Manifest) GetComponentDef(typeName string) *ComponentDef { func (manifest *Manifest) GetComponentDefs(typeName string) []ComponentDef { var componentDefs []ComponentDef + typeConvention := strings.Split(typeName, ":") + depIsolation := fmt.Sprintf("${$dependency('%s')}", typeConvention[0]) + if manifest.HasIsolation() && manifest.CheckDependencyExists(depIsolation) { + typeName = fmt.Sprintf("%s:%s", depIsolation, typeConvention[1]) + } + for _, compDefs := range manifest.Objects { if compDefs.Type == typeName { componentDefs = append(componentDefs, compDefs) From 6cf3ad813ed892968badeae8a901646b4011c2e1 Mon Sep 17 00:00:00 2001 From: Fred L Sharp Date: Sun, 2 Jul 2023 23:38:42 -0500 Subject: [PATCH 03/11] Add timestamp to profiler report query (#136) - Enhance logging of profiler report processing --- cmd/optimize/report.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/optimize/report.go b/cmd/optimize/report.go index 4c42500b..028fcc26 100644 --- a/cmd/optimize/report.go +++ b/cmd/optimize/report.go @@ -51,7 +51,7 @@ var ( var reportTemplate = template.Must(template.New("").Parse(` SINCE -1w -FETCH id, attributes, events(k8sprofiler:report){{if .Eligible}}[attributes("report_contents.optimizable") = "true"]{{end}}{attributes} +FETCH id, attributes, events(k8sprofiler:report){{if .Eligible}}[attributes("report_contents.optimizable") = "true"]{{end}}{attributes, timestamp} FROM entities(k8s:deployment{{with .WorkloadId}}:{{.}}{{end}}){{with .WorkloadFilters}}[{{.}}]{{end}} LIMITS events.count(1) `)) @@ -153,6 +153,7 @@ func extractReportData(response *uql.Response) ([]reportRow, error) { if !ok { return results, fmt.Errorf("entity id string type assertion failed on main dataset row %v: %+v", index, row) } + log.WithField("workloadId", workloadId).Info("Processing workload report") reportRow := reportRow{WorkloadId: workloadId} workloadAttributeDataset, ok := row[1].(*uql.DataSet) From 7d48e22ec4c301e8dc5fd2cd6d4a21c5faf2536b Mon Sep 17 00:00:00 2001 From: Fred L Sharp Date: Sun, 2 Jul 2023 23:39:32 -0500 Subject: [PATCH 04/11] Add nil checks on uql response mainDataSet (#137) --- cmd/optimize/configure.go | 16 ++++++++++++---- cmd/optimize/report.go | 6 +++++- cmd/optimize/server_report.go | 6 +++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/cmd/optimize/configure.go b/cmd/optimize/configure.go index d7894104..88890692 100644 --- a/cmd/optimize/configure.go +++ b/cmd/optimize/configure.go @@ -166,13 +166,17 @@ FROM entities(k8s:deployment)[attributes("k8s.cluster.name") = "{{.Cluster}}" && } } - if workloadIdsFound := len(resp.Main().Data); workloadIdsFound != 1 { + mainDataSet := resp.Main() + if mainDataSet == nil { + return errors.New("Unable to configure optimizer. UQL main data set was nil for the given criteria.") + } + if workloadIdsFound := len(mainDataSet.Data); workloadIdsFound != 1 { return fmt.Errorf("Unable to configure optimizer. Found %v workload IDs for the given criteria.", workloadIdsFound) } var ok bool - workloadId, ok = resp.Main().Data[0][0].(string) + workloadId, ok = mainDataSet.Data[0][0].(string) if !ok { - return fmt.Errorf("Unable to convert workloadId query value %q to string", resp.Main().Data[0][0]) + return fmt.Errorf("Unable to convert workloadId query value %q to string", mainDataSet.Data[0][0]) } profilerReport, err = getProfilerReport(workloadId) @@ -423,7 +427,11 @@ func getProfilerReport(workloadId string) (map[string]any, error) { } } - mainDataSetData := resp.Main().Data + mainDataSet := resp.Main() + if mainDataSet == nil { + return nil, errors.New("No events found, main data set was nil") + } + mainDataSetData := mainDataSet.Data if len(mainDataSetData) < 1 { return nil, errors.New("No events found, main data set had no rows") } diff --git a/cmd/optimize/report.go b/cmd/optimize/report.go index 028fcc26..3e779b93 100644 --- a/cmd/optimize/report.go +++ b/cmd/optimize/report.go @@ -142,7 +142,11 @@ func listReports(cmd *cobra.Command, args []string) error { } func extractReportData(response *uql.Response) ([]reportRow, error) { - resp_data := &response.Main().Data + mainDataSet := response.Main() + if mainDataSet == nil { + return []reportRow{}, nil + } + resp_data := &mainDataSet.Data results := make([]reportRow, 0, len(*resp_data)) for index, row := range *resp_data { if len(row) < 3 { diff --git a/cmd/optimize/server_report.go b/cmd/optimize/server_report.go index 90c45937..6d84b97c 100644 --- a/cmd/optimize/server_report.go +++ b/cmd/optimize/server_report.go @@ -89,7 +89,11 @@ func getWorkloadId(workloadName string) (*string, error) { if err != nil { return nil, err } - workloadIds := columnValues(response.Main(), 0) + mainDataSet := response.Main() + if mainDataSet == nil { + return nil, fmt.Errorf("nil main data set when querying for workloads with name %q", workloadName) + } + workloadIds := columnValues(mainDataSet, 0) // Check if either none or multiple workload IDs found if len(workloadIds) < 1 { From e41fc5d087c4d7b542ceb22933f5836d9801f740 Mon Sep 17 00:00:00 2001 From: Forrest Date: Mon, 3 Jul 2023 00:43:33 -0400 Subject: [PATCH 05/11] Added tag as a Flag option to fsoc solution fork (#130) --- cmd/solution/fork.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/solution/fork.go b/cmd/solution/fork.go index cdc0d2a2..b238c408 100644 --- a/cmd/solution/fork.go +++ b/cmd/solution/fork.go @@ -48,10 +48,11 @@ var solutionForkCmd = &cobra.Command{ } func GetSolutionForkCommand() *cobra.Command { - solutionForkCmd.Flags().String("source-name", "", "name of the solution that needs to be downloaded") + solutionForkCmd.Flags().String("source-name", "", "name of the solution that needs to be forked and downloaded") _ = solutionForkCmd.Flags().MarkDeprecated("source-name", "please use argument instead.") solutionForkCmd.Flags().String("name", "", "name of the solution to copy it to") _ = solutionForkCmd.Flags().MarkDeprecated("name", "please use argument instead.") + solutionForkCmd.Flags().String("tag", "stable", "tag related to the solution to fork and download") return solutionForkCmd } @@ -177,11 +178,12 @@ func editManifest(fileSystem afero.Fs, forkName string) { func downloadSolutionZip(cmd *cobra.Command, solutionName string, forkName string) { var solutionNameWithZipExtension = getSolutionNameWithZip(solutionName) + solutionTagFlag, _ := cmd.Flags().GetString("tag") var message string headers := map[string]string{ "stage": "STABLE", - "tag": "stable", + "tag": solutionTagFlag, "solutionFileName": solutionNameWithZipExtension, } httpOptions := api.Options{Headers: headers} From a81bbea82b21bf334fbc949eac4b94be34803d53 Mon Sep 17 00:00:00 2001 From: Guy Wilks Date: Sun, 2 Jul 2023 21:45:36 -0700 Subject: [PATCH 06/11] moved around reponse to login (#134) Attempt to fix FSOC-100, oauth login may show "request failed" in browser (even though the login was successful) instead of "login completed successfully". Co-authored-by: GDW1 --- platform/api/oauth.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/platform/api/oauth.go b/platform/api/oauth.go index 35c36af2..91e1d4d4 100644 --- a/platform/api/oauth.go +++ b/platform/api/oauth.go @@ -425,11 +425,12 @@ func callbackHandler(respChan chan authCodes, w http.ResponseWriter, r *http.Req Scope: safeExtractFirstValue(values, "scope"), State: safeExtractFirstValue(values, "state"), } - //log.Infof("response codes %+v", codes) - respChan <- codes // provide a stub page to be displayed in the browser after login fmt.Fprint(w, "Login successful. You can close this browser window.") + + //log.Infof("response codes %+v", codes) + respChan <- codes } func safeExtractFirstValue(queryValues url.Values, field string) string { From 1601447fbd4764299bf233e65594ae9a1ae282b2 Mon Sep 17 00:00:00 2001 From: Fred L Sharp Date: Thu, 6 Jul 2023 17:13:39 -0500 Subject: [PATCH 07/11] Update optimize configure to set deployment UID (#140) on optimizer Orion config object --- cmd/optimize/configure.go | 1 + cmd/optimize/types.go | 1 + 2 files changed, 2 insertions(+) diff --git a/cmd/optimize/configure.go b/cmd/optimize/configure.go index 88890692..5018a7a3 100644 --- a/cmd/optimize/configure.go +++ b/cmd/optimize/configure.go @@ -236,6 +236,7 @@ FROM entities(k8s:deployment)[attributes("k8s.cluster.name") = "{{.Cluster}}" && } // Target newOptimizerConfig.Target.K8SDeployment.ClusterID = profilerReport["resource_metadata.cluster_id"].(string) + newOptimizerConfig.Target.K8SDeployment.DeploymentUID = profilerReport["k8s.deployment.uid"].(string) newOptimizerConfig.Target.K8SDeployment.ClusterName = profilerReport["resource_metadata.cluster_name"].(string) newOptimizerConfig.Target.K8SDeployment.ContainerName = profilerReport["report_contents.main_container_name"].(string) newOptimizerConfig.Target.K8SDeployment.NamespaceName = profilerReport["resource_metadata.namespace_name"].(string) diff --git a/cmd/optimize/types.go b/cmd/optimize/types.go index d355fb4f..8dbeeca5 100644 --- a/cmd/optimize/types.go +++ b/cmd/optimize/types.go @@ -59,6 +59,7 @@ type Suspension struct { } type K8SDeployment struct { ClusterID string `json:"clusterId"` + DeploymentUID string `json:"deploymentUid"` ClusterName string `json:"clusterName"` ContainerName string `json:"containerName"` NamespaceName string `json:"namespaceName"` From 92abd9e25cb140680194f7f3a7eab31acab6612b Mon Sep 17 00:00:00 2001 From: pavel-georgiev Date: Sun, 9 Jul 2023 14:33:08 -0700 Subject: [PATCH 08/11] Fix and enable "solution push --subscribe" (#141) * Unhide --subscribe flag * Fix issue when --subscribe is used with --wait * Do not allow --subscribe with --solution-bundle * Remove double print of messages upon subscribe * Make subscription retries more resiliant (up to 4 retries with backoff) Co-authored-by: Pavel Georgiev --- cmd/solution/push.go | 2 +- cmd/solution/upload.go | 48 ++++++++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/cmd/solution/push.go b/cmd/solution/push.go index 94a28811..b8154d9c 100644 --- a/cmd/solution/push.go +++ b/cmd/solution/push.go @@ -68,11 +68,11 @@ func getSolutionPushCmd() *cobra.Command { solutionPushCmd.Flags(). Bool("subscribe", false, "Subscribe to the solution that you are pushing") - _ = solutionPushCmd.Flags().MarkHidden("subscribe") // TODO: unify with isolation before showing solutionPushCmd.MarkFlagsMutuallyExclusive("solution-bundle", "directory") // either solution dir or prepackaged zip solutionPushCmd.MarkFlagsMutuallyExclusive("solution-bundle", "bump") // cannot modify prepackaged zip solutionPushCmd.MarkFlagsMutuallyExclusive("solution-bundle", "wait") // TODO: allow when extracting manifest data + solutionPushCmd.MarkFlagsMutuallyExclusive("solution-bundle", "subscribe") // TODO: allow when extracting manifest data solutionPushCmd.MarkFlagsMutuallyExclusive("tag", "stable", "env-file") // stable is an alias for --tag=stable return solutionPushCmd diff --git a/cmd/solution/upload.go b/cmd/solution/upload.go index 99587fbd..c2e2d7fe 100644 --- a/cmd/solution/upload.go +++ b/cmd/solution/upload.go @@ -32,6 +32,8 @@ import ( "github.com/cisco-open/fsoc/platform/api" ) +const MAX_SUBSCRIBE_TRIES = 4 + func bumpSolutionVersionInManifest(cmd *cobra.Command, manifest *Manifest, manifestPath string) { if err := bumpManifestPatchVersion(manifest); err != nil { log.Fatal(err.Error()) @@ -208,6 +210,29 @@ func uploadSolution(cmd *cobra.Command, push bool) { output.PrintCmdStatus(cmd, fmt.Sprintf("Successfully validated %v.\n", solutionDisplayText)) } + if subscribe, _ := cmd.Flags().GetBool("subscribe"); subscribe { + log.WithField("solution", solutionName).Info("Subscribing to solution") + cfg := config.GetCurrentContext() + layerID := cfg.Tenant + headers = map[string]string{ + "layer-type": "TENANT", + "layer-id": layerID, + } + for i := 1; i <= MAX_SUBSCRIBE_TRIES; i++ { + url := getSolutionSubscribeUrl() + "/" + solutionName + err = api.JSONPatch(url, &subscriptionStruct{IsSubscribed: true}, &res, &api.Options{Headers: headers}) + if err == nil { + output.PrintCmdStatus(cmd, fmt.Sprintf("Tenant %s has successfully subscribed to solution %s\n", layerID, solutionName)) + break + } + time.Sleep(time.Second * time.Duration(i)) + } + if err != nil { + log.Fatalf("Solution command failed: %v", err) + } + + } + // wait for installation, if requested (and possible) if push && waitFlag >= 0 && solutionName != "" && solutionVersion != "" { var duration string @@ -242,29 +267,6 @@ func uploadSolution(cmd *cobra.Command, push bool) { } output.PrintCmdStatus(cmd, fmt.Sprintf("Installed %v successfully.\n", solutionDisplayText)) } - if subscribe, _ := cmd.Flags().GetBool("subscribe"); subscribe { - log.WithField("solution", solutionName).Info("Subscribing to solution") - cfg := config.GetCurrentContext() - layerID := cfg.Tenant - headers = map[string]string{ - "layer-type": "TENANT", - "layer-id": layerID, - } - err = api.JSONPatch(getSolutionSubscribeUrl()+"/"+solutionName, &subscriptionStruct{IsSubscribed: true}, &res, &api.Options{Headers: headers}) - if err != nil { - if problem, ok := err.(api.Problem); ok && problem.Status == 404 { - time.Sleep(time.Second * 2) - err = api.JSONPatch(getSolutionSubscribeUrl()+"/"+solutionName, &subscriptionStruct{IsSubscribed: true}, &res, &api.Options{Headers: headers}) - if err != nil { - log.Fatalf("Solution command failed: %v", err) - } - output.PrintCmdStatus(cmd, fmt.Sprintf("Tenant %s has successfully subscribed to solution %s\n", layerID, solutionName)) - } else { - log.Fatalf("Solution command failed: %v", err) - } - } - output.PrintCmdStatus(cmd, fmt.Sprintf("Tenant %s has successfully subscribed to solution %s\n", layerID, solutionName)) - } } func getSolutionValidationErrorsString(total int, errors Errors) string { From 0d09368f469157fc5337393407c69d580a2fdbb8 Mon Sep 17 00:00:00 2001 From: Guy Wilks Date: Sun, 9 Jul 2023 14:34:16 -0700 Subject: [PATCH 09/11] Fsoc 159 - Headers for generated documentation starts at h1 markdown heading level if the --h1 flag used (#143) * Filter and Print options for queries. example shown with fsoc solution list * fix so that generating gendocs starts on the first level of heading * hid gendocs header functionality behind header --------- Co-authored-by: GDW1 --- cmd/gendocs/gendocs.go | 89 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/cmd/gendocs/gendocs.go b/cmd/gendocs/gendocs.go index b486d6e2..77809e5c 100644 --- a/cmd/gendocs/gendocs.go +++ b/cmd/gendocs/gendocs.go @@ -19,6 +19,7 @@ package gendocs import ( + "bufio" "fmt" "os" "path/filepath" @@ -51,6 +52,8 @@ The directory should either be empty or not exist.`, } func NewSubCmd() *cobra.Command { + gendocsCmd.Flags(). + Bool("h1", false, "This flag will change all generated headers to start at an h1 level heading") return gendocsCmd } @@ -105,6 +108,23 @@ func genDocs(cmd *cobra.Command, args []string) { log.Fatalf("Error generating fsoc docs table of contents: %v", err) } + if cmd.Flag("h1").Changed { + log.Infof("Editing headers in files\n") + + files := getListOfFiles(path) + log.Infof("There are %d files to edit\n", len(files)) + + for i := 0; i < len(files); i++ { + file := files[i] + log.Infof("Starting to process file %s\n", file.Name()) + + err := processFile(file) + if err != nil { + log.Fatalf(err.Error()) + } + } + } + output.PrintCmdStatus(cmd, "Documentation generated successfully.\n") } @@ -163,3 +183,72 @@ func genTOCNode(root *cobra.Command) *tocEntry { return &entry } + +func getFileFromArgs(fileLoc string) *os.File { + file, err := os.Open(fileLoc) + if err != nil { + log.Fatal(err.Error()) + } + return file +} + +func getListOfFiles(dir string) []*os.File { + var files []*os.File + err := filepath.Walk(dir, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + file := getFileFromArgs(path) + isFileMarkdown := strings.Contains(file.Name(), ".md") + if isFileMarkdown { + log.Infof("Adding %s to the list of files to edit\n", file.Name()) + files = append(files, file) + } + return nil + }) + if err != nil { + log.Infof(err.Error()) + } + return files +} + +func processFile(file *os.File) error { + fileScanner := bufio.NewScanner(file) + fileScanner.Split(bufio.ScanLines) + var fileLines []string + + for fileScanner.Scan() { + fileLines = append(fileLines, fileScanner.Text()) + } + + for i := 1; i < len(fileLines); i++ { + line := fileLines[i] + if len(line) > 2 { + if line[0:2] == "##" { + fileLines[i] = line[2:] + } + } + if fileLines[i] == "# SEE ALSO" { + fileLines[i] = "# See Also" + } + if fileLines[i] == "# Options inherited from parent commands" { + fileLines[i] = "# Options Inherited From Parent Commands" + } + } + + if err := os.Truncate(file.Name(), 0); err != nil { + log.Infof("Failed to truncate: %v", err) + } + + newFile, _ := os.OpenFile(file.Name(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + dataWriter := bufio.NewWriter(newFile) + + for _, data := range fileLines { + _, _ = dataWriter.WriteString(data + "\n") + } + + dataWriter.Flush() + + return nil +} From 40880ea203485d332cd275d08a42f2bb3494db81 Mon Sep 17 00:00:00 2001 From: Guy Wilks Date: Sun, 9 Jul 2023 14:41:04 -0700 Subject: [PATCH 10/11] FSOC-37 Added argument support to `fsoc config set` (#132) * FSOC-37 Added argument support to * fixed deprecation messages, fixed documentation examples --------- Co-authored-by: GDW1 --- cmd/config/set.go | 56 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/cmd/config/set.go b/cmd/config/set.go index a3b0f582..4a854626 100644 --- a/cmd/config/set.go +++ b/cmd/config/set.go @@ -37,18 +37,18 @@ if on context name is specified, the current context is created/updated.` setContextExample = ` # Set oauth credentials (recommended for interactive use) - fsoc config set --auth=oauth --url=https://mytenant.observe.appdynamics.com + fsoc config set auth=oauth url=https://mytenant.observe.appdynamics.com # Set service or agent principal credentials (secret file must remain accessible) - fsoc config set --auth=service-principal --secret-file=my-service-principal.json - fsoc config set --auth=agent-principal --secret-file=agent-helm-values.yaml - fsoc config set --auth=agent-principal --secret-file=client-values.json --tenant=123456 --url=https://mytenant.observe.appdynamics.com + fsoc config set auth=service-principal secret-file=my-service-principal.json + fsoc config set auth=agent-principal secret-file=agent-helm-values.yaml + fsoc config set auth=agent-principal secret-file=client-values.json tenant=123456 url=https://mytenant.observe.appdynamics.com # Set local access - fsoc config set --auth=local url=http://localhost --appd-pid=PID --appd-tid=TID --appd-pty=PTY + fsoc config set auth=local url=http://localhost appd-pid=PID appd-tid=TID appd-pty=PTY # Set the token field on the "prod" context entry without touching other values - fsoc config set --profile prod --token=top-secret` + fsoc config set profile prod token=top-secret` ) func newCmdConfigSet() *cobra.Command { @@ -57,21 +57,29 @@ func newCmdConfigSet() *cobra.Command { Use: "set [--profile CONTEXT] --auth=AUTH [flags]", Short: "Create or modify a context entry in an fsoc config file", Long: setContextLong, - Args: cobra.MaximumNArgs(1), + Args: cobra.MaximumNArgs(9), Example: setContextExample, Annotations: map[string]string{AnnotationForConfigBypass: ""}, Run: configSetContext, } cmd.Flags().String(AppdPid, "", "pid to use (local auth type only, provide raw value to be encoded)") + _ = cmd.Flags().MarkDeprecated(AppdPid, "the --"+AppdPid+" flag is deprecated, please use arguments supplied as "+AppdPid+"="+strings.ToUpper(AppdPid)) cmd.Flags().String(AppdTid, "", "tid to use (local auth type only, provide raw value to be encoded)") + _ = cmd.Flags().MarkDeprecated(AppdTid, "the --"+AppdTid+" flag is deprecated, please use arguments supplied as "+AppdTid+"="+strings.ToUpper(AppdTid)) cmd.Flags().String(AppdPty, "", "pty to use (local auth type only, provide raw value to be encoded)") + _ = cmd.Flags().MarkDeprecated(AppdPty, "the --"+AppdPty+" flag is deprecated, please use arguments supplied as "+AppdPty+"="+strings.ToUpper(AppdPty)) cmd.Flags().String("auth", "", fmt.Sprintf(`Select authentication method, one of {"%v"}`, strings.Join(GetAuthMethodsStringList(), `", "`))) + _ = cmd.Flags().MarkDeprecated("auth", "the --auth flag is deprecated, please use arguments supplied as auth=AUTH") cmd.Flags().String("server", "", "Set server host name") - _ = cmd.Flags().MarkDeprecated("server", "The --server flag is deprecated, please use --url instead.") + _ = cmd.Flags().MarkDeprecated("server", "the --server flag is deprecated, please use arguments supplied as url=URL") cmd.Flags().String("url", "", "Set server URL (with http or https schema)") + _ = cmd.Flags().MarkDeprecated("url", "the --url flag is deprecated, please use arguments supplied as url=URL") cmd.Flags().String("tenant", "", "Set tenant ID") + _ = cmd.Flags().MarkDeprecated("tenant", "the --tenant flag is deprecated, please use arguments supplied as tenant=TENANT") cmd.Flags().String("token", "", "Set token value (use --token=- to get from stdin)") + _ = cmd.Flags().MarkDeprecated("token", "the --token flag is deprecated, please use arguments supplied as token=TOKEN") cmd.Flags().String("secret-file", "", "Set a credentials file to use for service principal (.json or .csv) or agent principal (.yaml)") + _ = cmd.Flags().MarkDeprecated("secret-file", "the --secret-file flag is deprecated, please use arguments supplied as secret-file=SECRET-TOKEN") return cmd } @@ -95,13 +103,39 @@ func validateUrl(providedUrl string) (string, error) { return parsedUrl.String(), nil } +func validateArgs(cmd *cobra.Command, args []string) error { + flags := cmd.Flags() + allowedArgs := []string{AppdPid, AppdTid, AppdPty, "auth", "server", "url", "tenant", "token", "secret-file"} + for i := 0; i < len(args); i++ { + // check arg format ∑+=∑+ + stringSegments := strings.Split(args[i], "=") + name, value := stringSegments[0], stringSegments[1] + if len(stringSegments) != 2 { + return fmt.Errorf("parameter name and value cannot contain \"=\"") + } + // check arg name is valid (i.e. no disallowed flags) + if !slices.Contains(allowedArgs, name) { + return fmt.Errorf("argument name %s must be one of the following values %s", name, strings.Join(allowedArgs, ", ")) + } + // make sure flag isn't already set + if flags.Changed(name) { + return fmt.Errorf("cannot have both flag and argument with same name") + } + // Set flag manually + err := flags.Set(name, value) + if err != nil { + return err + } + } + return nil +} + func configSetContext(cmd *cobra.Command, args []string) { var contextName string // Check that either context name or current context is specified - if len(args) > 0 { - _ = cmd.Help() - log.Fatalf("Unexpected args: %v", args) + if err := validateArgs(cmd, args); err != nil { + log.Fatalf("%v", err) } // Check that at least one value is specified (including empty) From df69ef25b0ac2ab2773b9932e211fcef07407364 Mon Sep 17 00:00:00 2001 From: GDW1 Date: Thu, 13 Jul 2023 15:26:51 -0700 Subject: [PATCH 11/11] config tables that are easily changeable now filter config changes --- cmd/config/set.go | 125 ++++++++++++++++++++++++++++++--- cmd/config/types.go | 166 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+), 8 deletions(-) diff --git a/cmd/config/set.go b/cmd/config/set.go index 4a854626..b5c9b314 100644 --- a/cmd/config/set.go +++ b/cmd/config/set.go @@ -80,6 +80,7 @@ func newCmdConfigSet() *cobra.Command { _ = cmd.Flags().MarkDeprecated("token", "the --token flag is deprecated, please use arguments supplied as token=TOKEN") cmd.Flags().String("secret-file", "", "Set a credentials file to use for service principal (.json or .csv) or agent principal (.yaml)") _ = cmd.Flags().MarkDeprecated("secret-file", "the --secret-file flag is deprecated, please use arguments supplied as secret-file=SECRET-TOKEN") + cmd.Flags().Bool("patch", false, "Bypass field clearing") return cmd } @@ -178,8 +179,22 @@ func configSetContext(cmd *cobra.Command, args []string) { ctxPtr = &cfg.Contexts[len(cfg.Contexts)-1] } + patch, _ := cmd.Flags().GetBool("patch") // update only the fields for which flags were specified explicitly + if flags.Changed("auth") { + val, _ := flags.GetString("auth") + if val != "" && !slices.Contains(GetAuthMethodsStringList(), val) { + log.Fatalf(`Invalid --auth method %q; must be one of {"%v"}`, val, strings.Join(GetAuthMethodsStringList(), `", "`)) + } + ctxPtr.AuthMethod = val + // Clear All fields before setting other fields + clearFields([]string{"url", "server", "tenant", "user", "token", "refresh_token", "secret-file"}, ctxPtr) + } if flags.Changed("server") { + err := validateWriteReq(cmd, ctxPtr.AuthMethod, "url") + if err != nil { + log.Fatal(err.Error()) + } providedServer, _ := flags.GetString("server") constructedUrl := "https://" + providedServer cleanedUrl, err := validateUrl(constructedUrl) @@ -188,19 +203,40 @@ func configSetContext(cmd *cobra.Command, args []string) { } log.Warnf("The --server option is now deprecated. In the future, please use --url instead. We will set the url to %q for you now", cleanedUrl) ctxPtr.URL = cleanedUrl + if !patch { + automatedFieldClearing(ctxPtr, "url") + } } if flags.Changed("url") { + err := validateWriteReq(cmd, ctxPtr.AuthMethod, "url") + if err != nil { + log.Fatal(err.Error()) + } providedUrl, _ := flags.GetString("url") cleanedUrl, err := validateUrl(providedUrl) if err != nil { log.Fatal(err.Error()) } ctxPtr.URL = cleanedUrl + if !patch { + automatedFieldClearing(ctxPtr, "url") + } } if flags.Changed("tenant") { + err := validateWriteReq(cmd, ctxPtr.AuthMethod, "tenant") + if err != nil { + log.Fatal(err.Error()) + } ctxPtr.Tenant, _ = flags.GetString("tenant") + if !patch { + automatedFieldClearing(ctxPtr, "tenant") + } } if flags.Changed("token") { + err := validateWriteReq(cmd, ctxPtr.AuthMethod, "token") + if err != nil { + log.Fatal(err.Error()) + } value, _ := flags.GetString("token") if value == "-" { // token to come from stdin scanner := bufio.NewScanner(os.Stdin) @@ -209,38 +245,60 @@ func configSetContext(cmd *cobra.Command, args []string) { } else { ctxPtr.Token = value } + if !patch { + automatedFieldClearing(ctxPtr, "token") + } } if flags.Changed("secret-file") { - + err := validateWriteReq(cmd, ctxPtr.AuthMethod, "secret-file") + if err != nil { + log.Fatal(err.Error()) + } path, _ := flags.GetString("secret-file") path = expandHomePath(path) - var err error ctxPtr.SecretFile, err = filepath.Abs(path) if err != nil { ctxPtr.SecretFile = path } ctxPtr.CsvFile = "" // CSV file is a backward-compatibility value only - } - if flags.Changed("auth") { - val, _ := flags.GetString("auth") - if val != "" && !slices.Contains(GetAuthMethodsStringList(), val) { - log.Fatalf(`Invalid --auth method %q; must be one of {"%v"}`, val, strings.Join(GetAuthMethodsStringList(), `", "`)) + if !patch { + automatedFieldClearing(ctxPtr, "secret-file") } - ctxPtr.AuthMethod = val } if ctxPtr.AuthMethod == AuthMethodLocal { if flags.Changed(AppdPid) { + err := validateWriteReq(cmd, ctxPtr.AuthMethod, AppdPid) + if err != nil { + log.Fatal(err.Error()) + } pid, _ := flags.GetString(AppdPid) ctxPtr.LocalAuthOptions.AppdPid = pid + if !patch { + automatedFieldClearing(ctxPtr, AppdPid) + } } if flags.Changed(AppdPty) { + err := validateWriteReq(cmd, ctxPtr.AuthMethod, AppdPty) + if err != nil { + log.Fatal(err.Error()) + } pty, _ := flags.GetString(AppdPty) ctxPtr.LocalAuthOptions.AppdPty = pty + if !patch { + automatedFieldClearing(ctxPtr, AppdPty) + } } if flags.Changed(AppdTid) { + err := validateWriteReq(cmd, ctxPtr.AuthMethod, AppdTid) + if err != nil { + log.Fatal(err.Error()) + } tid, _ := flags.GetString(AppdTid) ctxPtr.LocalAuthOptions.AppdTid = tid + if !patch { + automatedFieldClearing(ctxPtr, AppdTid) + } } } @@ -273,3 +331,54 @@ func expandHomePath(file string) string { } return file } + +func getAuthFieldConfigRow(authService string) AuthFieldConfigRow { + return getAuthFieldWritePermissions()[authService] +} + +func validateWriteReq(cmd *cobra.Command, authService string, field string) error { + flags := cmd.Flags() + authProvider := authService + if flags.Changed("auth") { + authProvider, _ = flags.GetString("auth") + } + if authProvider == "" { + return fmt.Errorf("must provide an authentication type before or while writing to other context fields") + } + if getAuthFieldConfigRow(authProvider)[field] == 0 { + return fmt.Errorf("cannot write to field %s because it is not allowed for authentication method %s", field, authProvider) + } + return nil +} + +func clearFields(fields []string, ctxPtr *Context) { + if slices.Contains(fields, "auth") { + ctxPtr.AuthMethod = "" + } + if slices.Contains(fields, "url") { + ctxPtr.URL = "" + } + if slices.Contains(fields, "server") { + ctxPtr.Server = "" + } + if slices.Contains(fields, "tenant") { + ctxPtr.Tenant = "" + } + if slices.Contains(fields, "user") { + ctxPtr.User = "" + } + if slices.Contains(fields, "token") { + ctxPtr.Token = "" + } + if slices.Contains(fields, "refresh_token") { + ctxPtr.RefreshToken = "" + } + if slices.Contains(fields, "secret-file") { + ctxPtr.SecretFile = "" + } +} + +func automatedFieldClearing(ctxPtr *Context, field string) { + table := getAuthFieldClearConfig() + clearFields(table[ctxPtr.AuthMethod][field], ctxPtr) +} diff --git a/cmd/config/types.go b/cmd/config/types.go index 8d18e398..d2efec58 100644 --- a/cmd/config/types.go +++ b/cmd/config/types.go @@ -95,3 +95,169 @@ func GetAuthMethodsStringList() []string { AuthMethodLocal, } } + +type AuthFieldConfig int8 + +const ( + ClearField AuthFieldConfig = 0 + AllowField = 1 +) + +type AuthFieldConfigRow map[string]AuthFieldConfig + +func getAuthFieldWritePermissions() map[string]AuthFieldConfigRow { + return map[string]AuthFieldConfigRow{ + AuthMethodNone: { + "client-ID": ClearField, + "secret-file": ClearField, + "token": ClearField, + "tenant": ClearField, + "url": AllowField, + "refresh-token": ClearField, + "user": ClearField, + AppdTid: ClearField, + AppdPty: ClearField, + AppdPid: ClearField, + }, + AuthMethodOAuth: { + "client-ID": ClearField, + "secret-file": ClearField, + "token": ClearField, + "tenant": ClearField, + "url": AllowField, + "refresh-token": ClearField, + "user": ClearField, + AppdTid: ClearField, + AppdPty: ClearField, + AppdPid: ClearField, + }, + AuthMethodJWT: { + "client-ID": ClearField, + "secret-file": ClearField, + "token": AllowField, + "tenant": AllowField, + "url": AllowField, + "refresh-token": ClearField, + "user": AllowField, + AppdTid: ClearField, + AppdPty: ClearField, + AppdPid: ClearField, + }, + AuthMethodServicePrincipal: { + "client-ID": ClearField, + "secret-file": AllowField, + "token": ClearField, + "tenant": ClearField, + "url": AllowField, + "refresh-token": ClearField, + "user": ClearField, + AppdTid: ClearField, + AppdPty: ClearField, + AppdPid: ClearField, + }, + AuthMethodAgentPrincipal: { + "client-ID": ClearField, + "secret-file": AllowField, + "token": ClearField, + "tenant": ClearField, + "url": AllowField, + "refresh-token": ClearField, + "user": ClearField, + AppdTid: ClearField, + AppdPty: ClearField, + AppdPid: ClearField, + }, + AuthMethodLocal: { + "client-ID": ClearField, + "secret-file": ClearField, + "token": ClearField, + "tenant": ClearField, + "url": AllowField, + "refresh-token": ClearField, + "user": ClearField, + AppdTid: AllowField, + AppdPty: AllowField, + AppdPid: AllowField, + }, + } +} + +type authClearFields map[string][]string + +// returns a map which dictates which fields to remove when changing config settings +func getAuthFieldClearConfig() map[string]authClearFields { + return map[string]authClearFields{ + AuthMethodNone: { + "client-ID": {}, + "secret-file": {}, + "token": {}, + "tenant": {}, + "url": {}, + "refresh-token": {}, + "user": {}, + AppdTid: {}, + AppdPty: {}, + AppdPid: {}, + }, + AuthMethodOAuth: { + "client-ID": {}, //NA + "secret-file": {}, //NA + "token": {}, //NA + "tenant": {}, //NA + "url": {"tenant", "user", "token", "refresh_token", "secret-file"}, + "refresh-token": {}, //NA + "user": {}, //NA + AppdTid: {}, //NA + AppdPty: {}, //NA + AppdPid: {}, //NA + }, + AuthMethodJWT: { + "client-ID": {}, + "secret-file": {}, + "token": {}, + "tenant": {"token", "user"}, + "url": {"token", "tenant", "user"}, + "refresh-token": {}, + "user": {}, + AppdTid: {}, + AppdPty: {}, + AppdPid: {}, + }, + AuthMethodServicePrincipal: { + "client-ID": {}, + "secret-file": {"url", "server", "tenant", "user", "token", "refresh_token", "secret-file"}, + "token": {}, + "tenant": {}, + "url": {"tenant", "user", "token", "refresh_token"}, + "refresh-token": {}, + "user": {}, + AppdTid: {}, + AppdPty: {}, + AppdPid: {}, + }, + AuthMethodAgentPrincipal: { + "client-ID": {}, + "secret-file": {"url", "server", "tenant", "user", "token", "refresh_token", "secret-file"}, + "token": {}, + "tenant": {}, + "url": {"tenant", "user", "token", "refresh_token"}, + "refresh-token": {}, + "user": {}, + AppdTid: {}, + AppdPty: {}, + AppdPid: {}, + }, + AuthMethodLocal: { + "client-ID": {}, + "secret-file": {}, + "token": {}, + "tenant": {}, + "url": {}, + "refresh-token": {}, + "user": {}, + AppdTid: {}, + AppdPty: {}, + AppdPid: {}, + }, + } +}