github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/documentation/generator/parameters.go (about)

     1  package generator
     2  
     3  import (
     4  	"fmt"
     5  	"path"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/SAP/jenkins-library/pkg/config"
    10  )
    11  
    12  // Replaces the Parameters placeholder with the content from the yaml
    13  func createParametersSection(stepData *config.StepData) string {
    14  
    15  	var parameters = "## Parameters\n\n"
    16  
    17  	// sort parameters alphabetically with mandatory parameters first
    18  	sortStepParameters(stepData, true)
    19  	parameters += "### Overview - Step\n\n"
    20  	parameters += createParameterOverview(stepData, false)
    21  
    22  	parameters += "### Overview - Execution Environment\n\n"
    23  	parameters += "!!! note \"Orchestrator-specific only\"\n\n    These parameters are relevant for orchestrator usage and not considered when using the command line option.\n\n"
    24  	parameters += createParameterOverview(stepData, true)
    25  
    26  	// sort parameters alphabetically
    27  	sortStepParameters(stepData, false)
    28  	parameters += "### Details\n\n"
    29  	parameters += createParameterDetails(stepData)
    30  
    31  	return parameters
    32  }
    33  
    34  func parameterMandatoryInformation(param config.StepParameters, furtherInfo string) (mandatory bool, mandatoryString string, mandatoryInfo string) {
    35  	mandatory = param.Mandatory
    36  	mandatoryInfo = furtherInfo
    37  
    38  	mandatoryIf := param.MandatoryIf
    39  	if len(mandatoryIf) > 0 {
    40  		mandatory = true
    41  		if len(mandatoryInfo) > 0 {
    42  			mandatoryInfo += "<br />"
    43  		}
    44  		furtherInfoConditions := []string{"mandatory in case of:"}
    45  		for _, mandatoryCondition := range mandatoryIf {
    46  			furtherInfoConditions = append(furtherInfoConditions, fmt.Sprintf("- [`%v`](#%v)=`%v`", mandatoryCondition.Name, strings.ToLower(mandatoryCondition.Name), mandatoryCondition.Value))
    47  		}
    48  
    49  		mandatoryInfo += strings.Join(furtherInfoConditions, "<br />")
    50  	}
    51  
    52  	mandatoryString = "**yes**"
    53  	if len(mandatoryInfo) > 0 {
    54  		mandatoryString = "**(yes)**"
    55  	}
    56  	return
    57  }
    58  
    59  func createParameterOverview(stepData *config.StepData, executionEnvironment bool) string {
    60  	var table = "| Name | Mandatory | Additional information |\n"
    61  	table += "| ---- | --------- | ---------------------- |\n"
    62  
    63  	for _, param := range stepData.Spec.Inputs.Parameters {
    64  		furtherInfo, err := parameterFurtherInfo(param.Name, stepData, executionEnvironment)
    65  		if err == nil {
    66  
    67  			var mandatory bool
    68  			var mandatoryString string
    69  			mandatory, mandatoryString, furtherInfo = parameterMandatoryInformation(param, furtherInfo)
    70  			table += fmt.Sprintf("| [%v](#%v) | %v | %v |\n", param.Name, strings.ToLower(param.Name), ifThenElse(mandatory, mandatoryString, "no"), furtherInfo)
    71  		}
    72  	}
    73  
    74  	table += "\n"
    75  
    76  	return table
    77  }
    78  
    79  func parameterFurtherInfo(paramName string, stepData *config.StepData, executionEnvironment bool) (string, error) {
    80  
    81  	// handle general parameters
    82  	// ToDo: add special handling once we have more than one general parameter to consider
    83  	if paramName == "verbose" {
    84  		return checkParameterInfo("activates debug output", true, executionEnvironment)
    85  	}
    86  
    87  	if paramName == "script" {
    88  		return checkParameterInfo("[![Jenkins only](https://img.shields.io/badge/-Jenkins%20only-yellowgreen)](#) reference to Jenkins main pipeline script", true, executionEnvironment)
    89  	}
    90  
    91  	// handle non-step parameters (e.g. Jenkins-specific parameters as well as execution environment parameters)
    92  	jenkinsParams := []string{"containerCommand", "containerName", "containerShell", "dockerVolumeBind", "dockerWorkspace", "sidecarReadyCommand", "sidecarWorkspace", "stashContent"}
    93  	if !contains(stepParameterNames, paramName) {
    94  		for _, secret := range stepData.Spec.Inputs.Secrets {
    95  			if paramName == secret.Name && secret.Type == "jenkins" {
    96  				return checkParameterInfo("[![Jenkins only](https://img.shields.io/badge/-Jenkins%20only-yellowgreen)](#) id of credentials ([using credentials](https://www.jenkins.io/doc/book/using/using-credentials/))", true, executionEnvironment)
    97  			}
    98  		}
    99  		if contains(jenkinsParams, paramName) {
   100  			return checkParameterInfo("[![Jenkins only](https://img.shields.io/badge/-Jenkins%20only-yellowgreen)](#)", false, executionEnvironment)
   101  		}
   102  		return checkParameterInfo("", false, executionEnvironment)
   103  	}
   104  
   105  	// handle step-parameters (incl. secrets)
   106  	for _, param := range stepData.Spec.Inputs.Parameters {
   107  		if paramName == param.Name {
   108  			furtherInfo := ""
   109  			if param.DeprecationMessage != "" {
   110  				furtherInfo += "![deprecated](https://img.shields.io/badge/-deprecated-red)"
   111  			}
   112  			if param.Secret {
   113  				secretInfo := "[![Secret](https://img.shields.io/badge/-Secret-yellowgreen)](#) pass via ENV or Jenkins credentials"
   114  				if param.GetReference("vaultSecret") != nil || param.GetReference("vaultSecretFile") != nil {
   115  					secretInfo = " [![Vault](https://img.shields.io/badge/-Vault-lightgrey)](#) [![Secret](https://img.shields.io/badge/-Secret-yellowgreen)](/) pass via ENV, Vault or Jenkins credentials"
   116  
   117  				}
   118  				for _, res := range param.ResourceRef {
   119  					if res.Type == "secret" {
   120  						secretInfo += fmt.Sprintf(" ([`%v`](#%v))", res.Name, strings.ToLower(res.Name))
   121  					}
   122  				}
   123  				return checkParameterInfo(furtherInfo+secretInfo, true, executionEnvironment)
   124  			}
   125  			return checkParameterInfo(furtherInfo, true, executionEnvironment)
   126  		}
   127  	}
   128  	return checkParameterInfo("", true, executionEnvironment)
   129  }
   130  
   131  func checkParameterInfo(furtherInfo string, stepParam bool, executionEnvironment bool) (string, error) {
   132  	if stepParam && !executionEnvironment || !stepParam && executionEnvironment {
   133  		return furtherInfo, nil
   134  	}
   135  
   136  	if executionEnvironment {
   137  		return "", fmt.Errorf("step parameter not relevant as execution environment parameter")
   138  	}
   139  	return "", fmt.Errorf("execution environment parameter not relevant as step parameter")
   140  }
   141  
   142  func createParameterDetails(stepData *config.StepData) string {
   143  
   144  	details := ""
   145  
   146  	//jenkinsParameters := append(jenkinsParameters(stepData), "script")
   147  
   148  	for _, param := range stepData.Spec.Inputs.Parameters {
   149  		details += fmt.Sprintf("#### %v\n\n", param.Name)
   150  
   151  		if !contains(stepParameterNames, param.Name) {
   152  			details += "**Jenkins-specific:** Used for proper environment setup.\n\n"
   153  		}
   154  
   155  		if len(param.LongDescription) > 0 {
   156  			details += param.LongDescription + "\n\n"
   157  		} else {
   158  			details += param.Description + "\n\n"
   159  		}
   160  
   161  		details += "[back to overview](#parameters)\n\n"
   162  
   163  		details += "| Scope | Details |\n"
   164  		details += "| ---- | --------- |\n"
   165  
   166  		if param.DeprecationMessage != "" {
   167  			details += fmt.Sprintf("| Deprecated | %v |\n", param.DeprecationMessage)
   168  		}
   169  		details += fmt.Sprintf("| Aliases | %v |\n", aliasList(param.Aliases))
   170  		details += fmt.Sprintf("| Type | `%v` |\n", param.Type)
   171  		mandatory, mandatoryString, furtherInfo := parameterMandatoryInformation(param, "")
   172  		if mandatory && len(furtherInfo) > 0 {
   173  			mandatoryString = furtherInfo
   174  		}
   175  		details += fmt.Sprintf("| Mandatory | %v |\n", ifThenElse(mandatory, mandatoryString, "no"))
   176  		details += fmt.Sprintf("| Default | %v |\n", formatDefault(param, stepParameterNames))
   177  		if param.PossibleValues != nil {
   178  			details += fmt.Sprintf("| Possible values | %v |\n", possibleValueList(param.PossibleValues))
   179  		}
   180  		details += fmt.Sprintf("| Secret | %v |\n", ifThenElse(param.Secret, "**yes**", "no"))
   181  		details += fmt.Sprintf("| Configuration scope | %v |\n", scopeDetails(param.Scope))
   182  		details += fmt.Sprintf("| Resource references | %v |\n", resourceReferenceDetails(param.ResourceRef))
   183  
   184  		details += "\n\n"
   185  	}
   186  
   187  	for _, secret := range stepData.Spec.Inputs.Secrets {
   188  		details += fmt.Sprintf("#### %v\n\n", secret.Name)
   189  
   190  		if !contains(stepParameterNames, secret.Name) {
   191  			details += "**Jenkins-specific:** Used for proper environment setup. See *[using credentials](https://www.jenkins.io/doc/book/using/using-credentials/)* for details.\n\n"
   192  		}
   193  
   194  		details += secret.Description + "\n\n"
   195  
   196  		details += "[back to overview](#parameters)\n\n"
   197  
   198  		details += "| Scope | Details |\n"
   199  		details += "| ---- | --------- |\n"
   200  		details += fmt.Sprintf("| Aliases | %v |\n", aliasList(secret.Aliases))
   201  		details += fmt.Sprintf("| Type | `%v` |\n", "string")
   202  		details += fmt.Sprintf("| Configuration scope | %v |\n", scopeDetails([]string{"PARAMETERS", "GENERAL", "STEPS", "STAGES"}))
   203  
   204  		details += "\n\n"
   205  	}
   206  
   207  	return details
   208  }
   209  
   210  func formatDefault(param config.StepParameters, stepParameterNames []string) string {
   211  	if param.Default == nil {
   212  		// Return environment variable for all step parameters (not for Jenkins-specific parameters) in case no default is available
   213  		if contains(stepParameterNames, param.Name) {
   214  			return fmt.Sprintf("`$PIPER_%v` (if set)", param.Name)
   215  		}
   216  		return ""
   217  	}
   218  	//first consider conditional defaults
   219  	switch v := param.Default.(type) {
   220  	case []conditionDefault:
   221  		defaults := []string{}
   222  		for _, condDef := range v {
   223  			//ToDo: add type-specific handling of default
   224  			if len(condDef.key) > 0 && len(condDef.value) > 0 {
   225  				defaults = append(defaults, fmt.Sprintf("%v=`%v`: `%v`", condDef.key, condDef.value, condDef.def))
   226  			} else {
   227  				// containers with no condition will only hold def
   228  				defaults = append(defaults, fmt.Sprintf("`%v`", condDef.def))
   229  			}
   230  		}
   231  		return strings.Join(defaults, "<br />")
   232  	case []interface{}:
   233  		// handle for example stashes which possibly contain a mixture of fix and conditional values
   234  		defaults := []string{}
   235  		for _, def := range v {
   236  			if condDef, ok := def.(conditionDefault); ok {
   237  				defaults = append(defaults, fmt.Sprintf("%v=`%v`: `%v`", condDef.key, condDef.value, condDef.def))
   238  			} else {
   239  				defaults = append(defaults, fmt.Sprintf("- `%v`", def))
   240  			}
   241  		}
   242  		return strings.Join(defaults, "<br />")
   243  	case map[string]string:
   244  		defaults := []string{}
   245  		for key, def := range v {
   246  			defaults = append(defaults, fmt.Sprintf("`%v`: `%v`", key, def))
   247  		}
   248  		return strings.Join(defaults, "<br />")
   249  	case string:
   250  		if len(v) == 0 {
   251  			return "`''`"
   252  		}
   253  		return fmt.Sprintf("`%v`", v)
   254  	default:
   255  		return fmt.Sprintf("`%v`", param.Default)
   256  	}
   257  }
   258  
   259  func aliasList(aliases []config.Alias) string {
   260  	switch len(aliases) {
   261  	case 0:
   262  		return "-"
   263  	case 1:
   264  		alias := fmt.Sprintf("`%v`", aliases[0].Name)
   265  		if aliases[0].Deprecated {
   266  			alias += " (**deprecated**)"
   267  		}
   268  		return alias
   269  	default:
   270  		aList := make([]string, len(aliases))
   271  		for i, alias := range aliases {
   272  			aList[i] = fmt.Sprintf("- `%v`", alias.Name)
   273  			if alias.Deprecated {
   274  				aList[i] += " (**deprecated**)"
   275  			}
   276  		}
   277  		return strings.Join(aList, "<br />")
   278  	}
   279  }
   280  
   281  func possibleValueList(possibleValues []interface{}) string {
   282  	if len(possibleValues) == 0 {
   283  		return ""
   284  	}
   285  
   286  	pList := make([]string, len(possibleValues))
   287  	for i, possibleValue := range possibleValues {
   288  		pList[i] = fmt.Sprintf("- `%v`", fmt.Sprint(possibleValue))
   289  	}
   290  	return strings.Join(pList, "<br />")
   291  }
   292  
   293  func scopeDetails(scope []string) string {
   294  	scopeDetails := "<ul>"
   295  	scopeDetails += fmt.Sprintf("<li>%v parameter</li>", ifThenElse(contains(scope, "PARAMETERS"), "&#9746;", "&#9744;"))
   296  	scopeDetails += fmt.Sprintf("<li>%v general</li>", ifThenElse(contains(scope, "GENERAL"), "&#9746;", "&#9744;"))
   297  	scopeDetails += fmt.Sprintf("<li>%v steps</li>", ifThenElse(contains(scope, "STEPS"), "&#9746;", "&#9744;"))
   298  	scopeDetails += fmt.Sprintf("<li>%v stages</li>", ifThenElse(contains(scope, "STAGES"), "&#9746;", "&#9744;"))
   299  	scopeDetails += "</ul>"
   300  	return scopeDetails
   301  }
   302  
   303  func resourceReferenceDetails(resourceRef []config.ResourceReference) string {
   304  
   305  	if len(resourceRef) == 0 {
   306  		return "none"
   307  	}
   308  
   309  	resourceDetails := ""
   310  	for _, resource := range resourceRef {
   311  		if resource.Name == "commonPipelineEnvironment" {
   312  			resourceDetails += "_commonPipelineEnvironment_:<br />"
   313  			resourceDetails += fmt.Sprintf("&nbsp;&nbsp;reference to: `%v`<br />", resource.Param)
   314  			continue
   315  		}
   316  
   317  		if resource.Type == "secret" {
   318  			resourceDetails += "Jenkins credential id:<br />"
   319  			for i, alias := range resource.Aliases {
   320  				if i == 0 {
   321  					resourceDetails += "&nbsp;&nbsp;aliases:<br />"
   322  				}
   323  				resourceDetails += fmt.Sprintf("&nbsp;&nbsp;- `%v`%v<br />", alias.Name, ifThenElse(alias.Deprecated, " (**Deprecated**)", ""))
   324  			}
   325  			resourceDetails += fmt.Sprintf("&nbsp;&nbsp;id: [`%v`](#%v)<br />", resource.Name, strings.ToLower(resource.Name))
   326  			if resource.Param != "" {
   327  				resourceDetails += fmt.Sprintf("&nbsp;&nbsp;reference to: `%v`<br />", resource.Param)
   328  			}
   329  			continue
   330  		}
   331  
   332  		resourceDetails = addVaultResourceDetails(resource, resourceDetails)
   333  	}
   334  
   335  	return resourceDetails
   336  }
   337  
   338  func addVaultResourceDetails(resource config.ResourceReference, resourceDetails string) string {
   339  	if resource.Type == "vaultSecret" || resource.Type == "vaultSecretFile" {
   340  		resourceDetails += "<br/>Vault paths: <br />"
   341  		resourceDetails += "<ul>"
   342  		for _, rootPath := range config.VaultRootPaths {
   343  			resourceDetails += fmt.Sprintf("<li>`%s`</li>", path.Join(rootPath, resource.Default))
   344  		}
   345  		resourceDetails += "</ul>"
   346  	}
   347  	return resourceDetails
   348  }
   349  
   350  func sortStepParameters(stepData *config.StepData, considerMandatory bool) {
   351  	if stepData.Spec.Inputs.Parameters != nil {
   352  		parameters := stepData.Spec.Inputs.Parameters
   353  
   354  		if considerMandatory {
   355  			sort.SliceStable(parameters[:], func(i, j int) bool {
   356  				if (parameters[i].Mandatory || len(parameters[i].MandatoryIf) > 0) == (parameters[j].Mandatory || len(parameters[j].MandatoryIf) > 0) {
   357  					return strings.Compare(parameters[i].Name, parameters[j].Name) < 0
   358  				} else if parameters[i].Mandatory || len(parameters[i].MandatoryIf) > 0 {
   359  					return true
   360  				}
   361  				return false
   362  			})
   363  		} else {
   364  			sort.SliceStable(parameters[:], func(i, j int) bool {
   365  				return strings.Compare(parameters[i].Name, parameters[j].Name) < 0
   366  			})
   367  		}
   368  	}
   369  }