github.com/jfrog/jfrog-cli-core/v2@v2.51.0/artifactory/commands/permissiontarget/template.go (about)

     1  package permissiontarget
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/c-bata/go-prompt"
    11  	"github.com/jfrog/jfrog-cli-core/v2/artifactory/commands/utils"
    12  	"github.com/jfrog/jfrog-cli-core/v2/utils/config"
    13  	"github.com/jfrog/jfrog-cli-core/v2/utils/ioutils"
    14  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    15  	"github.com/jfrog/jfrog-client-go/utils/log"
    16  )
    17  
    18  type PermissionTargetTemplateCommand struct {
    19  	path string
    20  }
    21  
    22  const (
    23  	// Strings for prompt questions
    24  	SelectPermissionTargetSectionMsg = "Select the permission target section to configure" + ioutils.PressTabMsg
    25  	LeaveEmptyForDefault             = "(press enter for default)"
    26  
    27  	// Yes,No answers
    28  	Yes = "yes"
    29  	No  = "no"
    30  
    31  	// Main permission target configuration JSON keys
    32  	Name          = "name"
    33  	Repo          = "repo"
    34  	Build         = "build"
    35  	ReleaseBundle = "releaseBundle"
    36  
    37  	IncludePatternsDefault = "**"
    38  	ExcludePatternsDefault = ""
    39  
    40  	// Possible permissions
    41  	read            = "read"
    42  	write           = "write"
    43  	annotate        = "annotate"
    44  	delete          = "delete"
    45  	manage          = "manage"
    46  	managedXrayMeta = "managedXrayMeta"
    47  	distribute      = "distribute"
    48  
    49  	permissionSelectEnd = ioutils.DummyDefaultAnswer
    50  )
    51  
    52  func NewPermissionTargetTemplateCommand() *PermissionTargetTemplateCommand {
    53  	return &PermissionTargetTemplateCommand{}
    54  }
    55  
    56  func (pttc *PermissionTargetTemplateCommand) SetTemplatePath(path string) *PermissionTargetTemplateCommand {
    57  	pttc.path = path
    58  	return pttc
    59  }
    60  
    61  func (pttc *PermissionTargetTemplateCommand) ServerDetails() (*config.ServerDetails, error) {
    62  	// Since it's a local command, usage won't be reported.
    63  	return nil, nil
    64  }
    65  
    66  func (pttc *PermissionTargetTemplateCommand) Run() (err error) {
    67  	err = utils.ValidateTemplatePath(pttc.path)
    68  	if err != nil {
    69  		return
    70  	}
    71  	permissionTargetTemplateQuestionnaire := &ioutils.InteractiveQuestionnaire{
    72  		MandatoryQuestionsKeys: []string{Name},
    73  		QuestionsMap:           questionMap,
    74  		OptionalKeysSuggests:   optionalSuggestsMap,
    75  	}
    76  	err = permissionTargetTemplateQuestionnaire.Perform()
    77  	if err != nil {
    78  		return err
    79  	}
    80  	resBytes, err := json.Marshal(permissionTargetTemplateQuestionnaire.AnswersMap)
    81  	if err != nil {
    82  		return errorutils.CheckError(err)
    83  	}
    84  	if err = os.WriteFile(pttc.path, resBytes, 0644); err != nil {
    85  		return errorutils.CheckError(err)
    86  	}
    87  	log.Info(fmt.Sprintf("Permission target configuration template successfully created at %s.", pttc.path))
    88  
    89  	return nil
    90  }
    91  
    92  func (pttc *PermissionTargetTemplateCommand) CommandName() string {
    93  	return "rt_permission_target_template"
    94  }
    95  
    96  var optionalSuggestsMap = []prompt.Suggest{
    97  	{Text: ioutils.SaveAndExit},
    98  	{Text: Repo},
    99  	{Text: Build},
   100  	{Text: ReleaseBundle},
   101  }
   102  
   103  // Each permission target section (repo/build/releaseBundle) can have the following keys:
   104  //   - repos - Mandatory for repo and releaseBundle. Has a const default value for build.
   105  //   - include/exclude-patterns - Optional, has a default value.
   106  //   - actions - Optional,includes two maps (users and groups): user/group name -> permissions array.
   107  func permissionSectionCallBack(iq *ioutils.InteractiveQuestionnaire, section string) (value string, err error) {
   108  	if section == ioutils.SaveAndExit {
   109  		return
   110  	}
   111  	var sectionAnswer PermissionSectionAnswer
   112  	if section != Build {
   113  		sectionAnswer.Repositories = ioutils.AskString(reposQuestionInfo.Msg, reposQuestionInfo.PromptPrefix, false, reposQuestionInfo.AllowVars)
   114  	}
   115  	sectionAnswer.IncludePatterns = ioutils.AskStringWithDefault(includePatternsQuestionInfo.Msg, includePatternsQuestionInfo.PromptPrefix, IncludePatternsDefault)
   116  	sectionAnswer.ExcludePatterns = ioutils.AskString(excludePatternsQuestionInfo.Msg, excludePatternsQuestionInfo.PromptPrefix, true, excludePatternsQuestionInfo.AllowVars)
   117  	configureActions := ioutils.AskFromList("", configureActionsQuestionInfo.PromptPrefix+"users?"+ioutils.PressTabMsg, false, configureActionsQuestionInfo.Options, Yes)
   118  	if configureActions == Yes {
   119  		sectionAnswer.ActionsUsers = make(map[string]string)
   120  		readActionsMap("user", sectionAnswer.ActionsUsers)
   121  	}
   122  	configureActions = ioutils.AskFromList("", configureActionsQuestionInfo.PromptPrefix+"groups?", false, configureActionsQuestionInfo.Options, Yes)
   123  	if configureActions == Yes {
   124  		sectionAnswer.ActionsGroups = make(map[string]string)
   125  		readActionsMap("group", sectionAnswer.ActionsGroups)
   126  	}
   127  	iq.AnswersMap[section] = sectionAnswer
   128  	return
   129  }
   130  
   131  // We will read (user/group name, permissions) pairs until empty name is read.
   132  func readActionsMap(actionsType string, actionsMap map[string]string) {
   133  	customKeyPrompt := "Insert " + actionsType + " name (press enter to finish) >"
   134  	for {
   135  		key := ioutils.AskString("", customKeyPrompt, true, false)
   136  		if key == "" {
   137  			return
   138  		}
   139  		value := strings.Join(readPermissionList(key), ",")
   140  		actionsMap[key] = value
   141  	}
   142  }
   143  
   144  func readPermissionList(permissionsOwner string) (permissions []string) {
   145  	var permissionsMap = map[string]bool{
   146  		read:            false,
   147  		write:           false,
   148  		annotate:        false,
   149  		delete:          false,
   150  		manage:          false,
   151  		managedXrayMeta: false,
   152  		distribute:      false,
   153  	}
   154  	for {
   155  		answer := ioutils.AskFromList("", "Select permission value for "+permissionsOwner+" (press tab for options or enter to finish) >", true, buildPermissionSuggestArray(permissionsMap), permissionSelectEnd)
   156  		if answer == permissionSelectEnd {
   157  			break
   158  		}
   159  		// If the answer is a valid key, we will mark it with true to remove it from the suggestion list
   160  		if _, ok := permissionsMap[answer]; ok {
   161  			permissionsMap[answer] = true
   162  		} else {
   163  			// answer is a var, we will add it to the final permissions slice result
   164  			permissions = append(permissions, answer)
   165  		}
   166  	}
   167  	for key, value := range permissionsMap {
   168  		if value {
   169  			permissions = append(permissions, key)
   170  		}
   171  	}
   172  	return
   173  }
   174  
   175  func buildPermissionSuggestArray(permissionMap map[string]bool) (permissions []prompt.Suggest) {
   176  	for key, value := range permissionMap {
   177  		if !value {
   178  			permissions = append(permissions, prompt.Suggest{Text: key})
   179  		}
   180  	}
   181  	sort.Slice(permissions, func(i, j int) bool {
   182  		return permissions[i].Text < permissions[j].Text
   183  	})
   184  	return
   185  }
   186  
   187  var questionMap = map[string]ioutils.QuestionInfo{
   188  	Name: {
   189  		Msg:          "",
   190  		PromptPrefix: "Insert the permission target name >",
   191  		AllowVars:    false,
   192  		Writer:       ioutils.WriteStringAnswer,
   193  		MapKey:       Name,
   194  		Callback:     nil,
   195  	},
   196  	ioutils.OptionalKey: {
   197  		Msg:          "",
   198  		PromptPrefix: SelectPermissionTargetSectionMsg,
   199  		AllowVars:    false,
   200  		Writer:       nil,
   201  		MapKey:       "",
   202  		Callback:     permissionSectionCallBack,
   203  	},
   204  	Repo:          ioutils.FreeStringQuestionInfo,
   205  	Build:         ioutils.FreeStringQuestionInfo,
   206  	ReleaseBundle: ioutils.FreeStringQuestionInfo,
   207  }
   208  
   209  var reposQuestionInfo = ioutils.QuestionInfo{
   210  	Msg:          "Insert the section's repositories value.\nYou can specify the name \"ANY\" to apply to all repositories, \"ANY REMOTE\" for all remote repositories or \"ANY LOCAL\" for all local repositories",
   211  	PromptPrefix: ioutils.CommaSeparatedListMsg + " >",
   212  }
   213  
   214  var includePatternsQuestionInfo = ioutils.QuestionInfo{
   215  	Msg:          "Insert a value for include-patterns",
   216  	PromptPrefix: ioutils.CommaSeparatedListMsg + " " + LeaveEmptyForDefault,
   217  }
   218  
   219  var excludePatternsQuestionInfo = ioutils.QuestionInfo{
   220  	Msg:          "Insert value for exclude-patterns",
   221  	PromptPrefix: ioutils.CommaSeparatedListMsg + " " + LeaveEmptyForDefault + " []:",
   222  }
   223  
   224  var configureActionsQuestionInfo = ioutils.QuestionInfo{
   225  	PromptPrefix: "Configure actions for ",
   226  	Options: []prompt.Suggest{
   227  		{Text: Yes},
   228  		{Text: No},
   229  	},
   230  }
   231  
   232  type PermissionSectionAnswer struct {
   233  	Repositories    string            `json:"repositories,omitempty"`
   234  	IncludePatterns string            `json:"include-patterns,omitempty"`
   235  	ExcludePatterns string            `json:"exclude-patterns,omitempty"`
   236  	ActionsUsers    map[string]string `json:"actions-users,omitempty"`
   237  	ActionsGroups   map[string]string `json:"actions-groups,omitempty"`
   238  }