github.com/osievert/jfrog-cli-core@v1.2.7/artifactory/commands/permissiontarget/template.go (about)

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