Update and use install-plugins.sh (#756)
* Download install-plugins.sh manually from remote * Reformat * Goimports * Use install-plugins.sh bundled with the operator * Fix compile error * Update install-plugins.sh * Remove redundant param to new install-plugins.sh Co-authored-by: Piotr Ryba <pryba@virtuslab.com>
This commit is contained in:
		
							parent
							
								
									4c2671c1a3
								
							
						
					
					
						commit
						bef796af8a
					
				|  | @ -23,7 +23,7 @@ package v1alpha2 | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	corev1 "k8s.io/api/core/v1" | 	corev1 "k8s.io/api/core/v1" | ||||||
| 	"k8s.io/api/rbac/v1" | 	rbacv1 "k8s.io/api/rbac/v1" | ||||||
| 	"k8s.io/apimachinery/pkg/runtime" | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -400,7 +400,7 @@ func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) { | ||||||
| 	in.ConfigurationAsCode.DeepCopyInto(&out.ConfigurationAsCode) | 	in.ConfigurationAsCode.DeepCopyInto(&out.ConfigurationAsCode) | ||||||
| 	if in.Roles != nil { | 	if in.Roles != nil { | ||||||
| 		in, out := &in.Roles, &out.Roles | 		in, out := &in.Roles, &out.Roles | ||||||
| 		*out = make([]v1.RoleRef, len(*in)) | 		*out = make([]rbacv1.RoleRef, len(*in)) | ||||||
| 		copy(*out, *in) | 		copy(*out, *in) | ||||||
| 	} | 	} | ||||||
| 	in.ServiceAccount.DeepCopyInto(&out.ServiceAccount) | 	in.ServiceAccount.DeepCopyInto(&out.ServiceAccount) | ||||||
|  |  | ||||||
|  | @ -15,42 +15,60 @@ import ( | ||||||
| const installPluginsCommand = "install-plugins.sh" | const installPluginsCommand = "install-plugins.sh" | ||||||
| 
 | 
 | ||||||
| // bash scripts installs single jenkins plugin with specific version
 | // bash scripts installs single jenkins plugin with specific version
 | ||||||
| const installPluginsBashFmt = `#!/bin/bash -eu | const installPluginsBashScript = `#!/bin/bash -eu | ||||||
| 
 | 
 | ||||||
| # Resolve dependencies and download plugins given on the command line | # Resolve dependencies and download plugins given on the command line | ||||||
| # | # | ||||||
| # FROM jenkins | # FROM jenkins | ||||||
| # RUN install-plugins.sh docker-slaves github-branch-source | # RUN install-plugins.sh docker-slaves github-branch-source | ||||||
|  | # | ||||||
|  | # Environment variables: | ||||||
|  | # REF: directory with preinstalled plugins. Default: /usr/share/jenkins/ref/plugins | ||||||
|  | # JENKINS_WAR: full path to the jenkins.war. Default: /usr/share/jenkins/jenkins.war | ||||||
|  | # JENKINS_UC: url of the Update Center. Default: "" | ||||||
|  | # JENKINS_UC_EXPERIMENTAL: url of the Experimental Update Center for experimental versions of plugins. Default: "" | ||||||
|  | # JENKINS_INCREMENTALS_REPO_MIRROR: url of the incrementals repo mirror. Default: "" | ||||||
|  | # JENKINS_UC_DOWNLOAD: download url of the Update Center. Default: JENKINS_UC/download | ||||||
|  | # CURL_OPTIONS When downloading the plugins with curl. Curl options. Default: -sSfL | ||||||
|  | # CURL_CONNECTION_TIMEOUT When downloading the plugins with curl. <seconds> Maximum time allowed for connection. Default: 20 | ||||||
|  | # CURL_RETRY When downloading the plugins with curl. Retry request if transient problems occur. Default: 3 | ||||||
|  | # CURL_RETRY_DELAY When downloading the plugins with curl. <seconds> Wait time between retries. Default: 0 | ||||||
|  | # CURL_RETRY_MAX_TIME When downloading the plugins with curl. <seconds> Retry only within this period. Default: 60 | ||||||
| 
 | 
 | ||||||
| set -o pipefail | set -o pipefail | ||||||
| 
 | 
 | ||||||
| REF_DIR=${REF:-%s/plugins} | echo "WARN: install-plugins.sh is deprecated, please switch to jenkins-plugin-cli" | ||||||
| FAILED="$REF_DIR/failed-plugins.txt" | 
 | ||||||
|  | JENKINS_WAR=${JENKINS_WAR:-/usr/share/jenkins/jenkins.war} | ||||||
| 
 | 
 | ||||||
| . /usr/local/bin/jenkins-support | . /usr/local/bin/jenkins-support | ||||||
| 
 | 
 | ||||||
|  | REF_DIR="${REF}/plugins" | ||||||
|  | FAILED="$REF_DIR/failed-plugins.txt" | ||||||
|  | 
 | ||||||
| getLockFile() { | getLockFile() { | ||||||
|     printf '%%s' "$REF_DIR/${1}.lock" |     printf '%s' "$REF_DIR/${1}.lock" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| getArchiveFilename() { | getArchiveFilename() { | ||||||
|     printf '%%s' "$REF_DIR/${1}.jpi" |     printf '%s' "$REF_DIR/${1}.jpi" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| download() { | download() { | ||||||
|     local plugin originalPlugin version lock ignoreLockFile |     local plugin originalPlugin version lock ignoreLockFile url | ||||||
|     plugin="$1" |     plugin="$1" | ||||||
|     version="${2:-latest}" |     version="${2:-latest}" | ||||||
|     ignoreLockFile="${3:-}" |     ignoreLockFile="${3:-}" | ||||||
|  |     url="${4:-}" | ||||||
|     lock="$(getLockFile "$plugin")" |     lock="$(getLockFile "$plugin")" | ||||||
| 
 | 
 | ||||||
|     if [[ $ignoreLockFile ]] || mkdir "$lock" &>/dev/null; then |     if [[ $ignoreLockFile ]] || mkdir "$lock" &>/dev/null; then | ||||||
|         if ! doDownload "$plugin" "$version"; then |         if ! doDownload "$plugin" "$version" "$url"; then | ||||||
|             # some plugin don't follow the rules about artifact ID |             # some plugin don't follow the rules about artifact ID | ||||||
|             # typically: docker-plugin |             # typically: docker-plugin | ||||||
|             originalPlugin="$plugin" |             originalPlugin="$plugin" | ||||||
|             plugin="${plugin}-plugin" |             plugin="${plugin}-plugin" | ||||||
|             if ! doDownload "$plugin" "$version"; then |             if ! doDownload "$plugin" "$version" "$url"; then | ||||||
|                 echo "Failed to download plugin: $originalPlugin or $plugin" >&2 |                 echo "Failed to download plugin: $originalPlugin or $plugin" >&2 | ||||||
|                 echo "Not downloaded: ${originalPlugin}" >> "$FAILED" |                 echo "Not downloaded: ${originalPlugin}" >> "$FAILED" | ||||||
|                 return 1 |                 return 1 | ||||||
|  | @ -60,9 +78,11 @@ download() { | ||||||
|         if ! checkIntegrity "$plugin"; then |         if ! checkIntegrity "$plugin"; then | ||||||
|             echo "Downloaded file is not a valid ZIP: $(getArchiveFilename "$plugin")" >&2 |             echo "Downloaded file is not a valid ZIP: $(getArchiveFilename "$plugin")" >&2 | ||||||
|             echo "Download integrity: ${plugin}" >> "$FAILED" |             echo "Download integrity: ${plugin}" >> "$FAILED" | ||||||
|  |             rm $(getArchiveFilename "$plugin") | ||||||
|             return 1 |             return 1 | ||||||
|         fi |         fi | ||||||
| 
 | 
 | ||||||
|  |         resolveDependencies "$plugin" | ||||||
|     fi |     fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -70,6 +90,7 @@ doDownload() { | ||||||
|     local plugin version url jpi |     local plugin version url jpi | ||||||
|     plugin="$1" |     plugin="$1" | ||||||
|     version="$2" |     version="$2" | ||||||
|  |     url="$3" | ||||||
|     jpi="$(getArchiveFilename "$plugin")" |     jpi="$(getArchiveFilename "$plugin")" | ||||||
| 
 | 
 | ||||||
|     # If plugin already exists and is the same version do not download |     # If plugin already exists and is the same version do not download | ||||||
|  | @ -78,7 +99,9 @@ doDownload() { | ||||||
|         return 0 |         return 0 | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     if [[ "$version" == "latest" && -n "$JENKINS_UC_LATEST" ]]; then |     if [[ -n $url ]] ; then | ||||||
|  |         echo "Will use url=$url" | ||||||
|  |     elif [[ "$version" == "latest" && -n "$JENKINS_UC_LATEST" ]]; then | ||||||
|         # If version-specific Update Center is available, which is the case for LTS versions, |         # If version-specific Update Center is available, which is the case for LTS versions, | ||||||
|         # use it to resolve latest versions. |         # use it to resolve latest versions. | ||||||
|         url="$JENKINS_UC_LATEST/latest/${plugin}.hpi" |         url="$JENKINS_UC_LATEST/latest/${plugin}.hpi" | ||||||
|  | @ -89,7 +112,9 @@ doDownload() { | ||||||
|         # Download from Incrementals repo: https://jenkins.io/blog/2018/05/15/incremental-deployment/
 |         # Download from Incrementals repo: https://jenkins.io/blog/2018/05/15/incremental-deployment/
 | ||||||
|         # Example URL: https://repo.jenkins-ci.org/incrementals/org/jenkins-ci/plugins/workflow/workflow-support/2.19-rc289.d09828a05a74/workflow-support-2.19-rc289.d09828a05a74.hpi
 |         # Example URL: https://repo.jenkins-ci.org/incrementals/org/jenkins-ci/plugins/workflow/workflow-support/2.19-rc289.d09828a05a74/workflow-support-2.19-rc289.d09828a05a74.hpi
 | ||||||
|         local groupId incrementalsVersion |         local groupId incrementalsVersion | ||||||
|         arrIN=(${version//;/ })
 |         # add a trailing ; so the \n gets added to the end | ||||||
|  |         readarray -t "-d;" arrIN <<<"${version};"; | ||||||
|  |         unset 'arrIN[-1]'; | ||||||
|         groupId=${arrIN[1]} |         groupId=${arrIN[1]} | ||||||
|         incrementalsVersion=${arrIN[2]} |         incrementalsVersion=${arrIN[2]} | ||||||
|         url="${JENKINS_INCREMENTALS_REPO_MIRROR}/$(echo "${groupId}" | tr '.' '/')/${plugin}/${incrementalsVersion}/${plugin}-${incrementalsVersion}.hpi" |         url="${JENKINS_INCREMENTALS_REPO_MIRROR}/$(echo "${groupId}" | tr '.' '/')/${plugin}/${incrementalsVersion}/${plugin}-${incrementalsVersion}.hpi" | ||||||
|  | @ -99,7 +124,10 @@ doDownload() { | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     echo "Downloading plugin: $plugin from $url" |     echo "Downloading plugin: $plugin from $url" | ||||||
|     retry_command curl "${CURL_OPTIONS:--sSfL}" --connect-timeout "${CURL_CONNECTION_TIMEOUT:-20}" --retry "${CURL_RETRY:-5}" --retry-delay "${CURL_RETRY_DELAY:-0}" --retry-max-time "${CURL_RETRY_MAX_TIME:-60}" "$url" -o "$jpi" |     # We actually want to allow variable value to be split into multiple options passed to curl. | ||||||
|  |     # This is needed to allow long options and any options that take value. | ||||||
|  |     # shellcheck disable=SC2086 | ||||||
|  |     retry_command curl ${CURL_OPTIONS:--sSfL} --connect-timeout "${CURL_CONNECTION_TIMEOUT:-20}" --retry "${CURL_RETRY:-3}" --retry-delay "${CURL_RETRY_DELAY:-0}" --retry-max-time "${CURL_RETRY_MAX_TIME:-60}" "$url" -o "$jpi" | ||||||
|     return $? |     return $? | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -112,12 +140,52 @@ checkIntegrity() { | ||||||
|     return $? |     return $? | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | resolveDependencies() { | ||||||
|  |     local plugin jpi dependencies | ||||||
|  |     plugin="$1" | ||||||
|  |     jpi="$(getArchiveFilename "$plugin")" | ||||||
|  | 
 | ||||||
|  |     dependencies="$(unzip -p "$jpi" META-INF/MANIFEST.MF | tr -d '\r' | tr '\n' '|' | sed -e 's#| ##g' | tr '|' '\n' | grep "^Plugin-Dependencies: " | sed -e 's#^Plugin-Dependencies: ##')" | ||||||
|  | 
 | ||||||
|  |     if [[ ! $dependencies ]]; then | ||||||
|  |         echo " > $plugin has no dependencies" | ||||||
|  |         return | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     echo " > $plugin depends on $dependencies" | ||||||
|  | 
 | ||||||
|  |     IFS=',' read -r -a array <<< "$dependencies" | ||||||
|  | 
 | ||||||
|  |     for d in "${array[@]}" | ||||||
|  |     do | ||||||
|  |         plugin="$(cut -d':' -f1 - <<< "$d")" | ||||||
|  |         if [[ $d == *"resolution:=optional"* ]]; then | ||||||
|  |             echo "Skipping optional dependency $plugin" | ||||||
|  |         else | ||||||
|  |             local pluginInstalled | ||||||
|  |             if pluginInstalled="$(echo -e "${bundledPlugins}\n${installedPlugins}" | grep "^${plugin}:")"; then | ||||||
|  |                 pluginInstalled="${pluginInstalled//[$'\r']}" | ||||||
|  |                 local versionInstalled; versionInstalled=$(versionFromPlugin "${pluginInstalled}") | ||||||
|  |                 local minVersion; minVersion=$(versionFromPlugin "${d}") | ||||||
|  |                 if versionLT "${versionInstalled}" "${minVersion}"; then | ||||||
|  |                     echo "Upgrading bundled dependency $d ($minVersion > $versionInstalled)" | ||||||
|  |                     download "$plugin" & | ||||||
|  |                 else | ||||||
|  |                     echo "Skipping already installed dependency $d ($minVersion <= $versionInstalled)" | ||||||
|  |                 fi | ||||||
|  |             else | ||||||
|  |                 download "$plugin" & | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |     done | ||||||
|  |     wait | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bundledPlugins() { | bundledPlugins() { | ||||||
|     local JENKINS_WAR=/usr/share/jenkins/jenkins.war |     if [ -f "$JENKINS_WAR" ] | ||||||
|     if [ -f $JENKINS_WAR ] |  | ||||||
|     then |     then | ||||||
|         TEMP_PLUGIN_DIR=/tmp/plugintemp.$$ |         TEMP_PLUGIN_DIR=/tmp/plugintemp.$$ | ||||||
|         for i in $(jar tf $JENKINS_WAR | grep -E '[^detached-]plugins.*\..pi' | sort) |         for i in $(jar tf "$JENKINS_WAR" | grep -E '[^detached-]plugins.*\..pi' | sort) | ||||||
|         do |         do | ||||||
|             rm -fr $TEMP_PLUGIN_DIR |             rm -fr $TEMP_PLUGIN_DIR | ||||||
|             mkdir -p $TEMP_PLUGIN_DIR |             mkdir -p $TEMP_PLUGIN_DIR | ||||||
|  | @ -128,9 +196,7 @@ bundledPlugins() { | ||||||
|         done |         done | ||||||
|         rm -fr $TEMP_PLUGIN_DIR |         rm -fr $TEMP_PLUGIN_DIR | ||||||
|     else |     else | ||||||
|         rm -f "$TEMP_ALREADY_INSTALLED" |         echo "war not found, installing all plugins: $JENKINS_WAR" | ||||||
|         echo "ERROR file not found: $JENKINS_WAR" |  | ||||||
|         exit 1 |  | ||||||
|     fi |     fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -151,27 +217,29 @@ installedPlugins() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| jenkinsMajorMinorVersion() { | jenkinsMajorMinorVersion() { | ||||||
|     local JENKINS_WAR |  | ||||||
|     JENKINS_WAR=/usr/share/jenkins/jenkins.war |  | ||||||
|     if [[ -f "$JENKINS_WAR" ]]; then |     if [[ -f "$JENKINS_WAR" ]]; then | ||||||
|         local version major minor |         local version major minor | ||||||
|         version="$(java -jar /usr/share/jenkins/jenkins.war --version)" |         version="$(java -jar "$JENKINS_WAR" --version)" | ||||||
|         major="$(echo "$version" | cut -d '.' -f 1)" |         major="$(echo "$version" | cut -d '.' -f 1)" | ||||||
|         minor="$(echo "$version" | cut -d '.' -f 2)" |         minor="$(echo "$version" | cut -d '.' -f 2)" | ||||||
|         echo "$major.$minor" |         echo "$major.$minor" | ||||||
|     else |     else | ||||||
|         echo "ERROR file not found: $JENKINS_WAR" |         echo "" | ||||||
|         return 1 |  | ||||||
|     fi |     fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| main() { | main() { | ||||||
|     local plugin pluginVersion jenkinsVersion |     local plugin jenkinsVersion | ||||||
|     local plugins=() |     local plugins=() | ||||||
| 
 | 
 | ||||||
|     mkdir -p "$REF_DIR" || exit 1 |     mkdir -p "$REF_DIR" || exit 1 | ||||||
|     rm -f "$FAILED" |     rm -f "$FAILED" | ||||||
| 
 | 
 | ||||||
|  | 	echo "Cleaning up locks" | ||||||
|  | 	find "$REF_DIR" -regex ".*.lock" | while read -r filepath; do | ||||||
|  | 		rm -r "$filepath" | ||||||
|  | 	done | ||||||
|  | 
 | ||||||
|     # Read plugins from stdin or from the command line arguments |     # Read plugins from stdin or from the command line arguments | ||||||
|     if [[ ($# -eq 0) ]]; then |     if [[ ($# -eq 0) ]]; then | ||||||
|         while read -r line || [ "$line" != "" ]; do |         while read -r line || [ "$line" != "" ]; do | ||||||
|  | @ -193,16 +261,18 @@ main() { | ||||||
|         mkdir "$(getLockFile "${plugin%%:*}")" |         mkdir "$(getLockFile "${plugin%%:*}")" | ||||||
|     done |     done | ||||||
| 
 | 
 | ||||||
|     echo "Analyzing war..." |     echo "Analyzing war $JENKINS_WAR..." | ||||||
|     bundledPlugins="$(bundledPlugins)" |     bundledPlugins="$(bundledPlugins)" | ||||||
| 
 | 
 | ||||||
|     echo "Registering preinstalled plugins..." |     echo "Registering preinstalled plugins..." | ||||||
|     installedPlugins="$(installedPlugins)" |     installedPlugins="$(installedPlugins)" | ||||||
| 
 | 
 | ||||||
|     # Check if there's a version-specific update center, which is the case for LTS versions |     # Get the update center URL based on the jenkins version | ||||||
|     jenkinsVersion="$(jenkinsMajorMinorVersion)" |     jenkinsVersion="$(jenkinsMajorMinorVersion)" | ||||||
|     if curl -fsL -o /dev/null "$JENKINS_UC/$jenkinsVersion"; then |     # shellcheck disable=SC2086 | ||||||
|         JENKINS_UC_LATEST="$JENKINS_UC/$jenkinsVersion" |     jenkinsUcJson=$(curl ${CURL_OPTIONS:--sSfL} -o /dev/null -w "%{url_effective}" "${JENKINS_UC}/update-center.json?version=${jenkinsVersion}") | ||||||
|  |     if [ -n "${jenkinsUcJson}" ]; then | ||||||
|  |         JENKINS_UC_LATEST=${jenkinsUcJson//update-center.json/}
 | ||||||
|         echo "Using version-specific update center: $JENKINS_UC_LATEST..." |         echo "Using version-specific update center: $JENKINS_UC_LATEST..." | ||||||
|     else |     else | ||||||
|         JENKINS_UC_LATEST= |         JENKINS_UC_LATEST= | ||||||
|  | @ -210,14 +280,16 @@ main() { | ||||||
| 
 | 
 | ||||||
|     echo "Downloading plugins..." |     echo "Downloading plugins..." | ||||||
|     for plugin in "${plugins[@]}"; do |     for plugin in "${plugins[@]}"; do | ||||||
|         pluginVersion="" |         local reg='^([^:]+):?([^:]+)?:?([^:]+)?:?(http.+)?' | ||||||
| 
 |         if [[ $plugin =~ $reg ]]; then | ||||||
|         if [[ $plugin =~ .*:.* ]]; then |             local pluginId="${BASH_REMATCH[1]}" | ||||||
|             pluginVersion=$(versionFromPlugin "${plugin}") |             local version="${BASH_REMATCH[2]}" | ||||||
|             plugin="${plugin%%:*}" |             local lock="${BASH_REMATCH[3]}" | ||||||
|  |             local url="${BASH_REMATCH[4]}" | ||||||
|  |             download "$pluginId" "$version" "${lock:-true}" "${url}" & | ||||||
|  |         else | ||||||
|  |           echo "Skipping the line '${plugin}' as it does not look like a reference to a plugin" | ||||||
|         fi |         fi | ||||||
| 
 |  | ||||||
|         download "$plugin" "$pluginVersion" "true" & |  | ||||||
|     done |     done | ||||||
|     wait |     wait | ||||||
| 
 | 
 | ||||||
|  | @ -234,7 +306,10 @@ main() { | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     echo "Cleaning up locks" |     echo "Cleaning up locks" | ||||||
|     rm -r "$REF_DIR"/*.lock |     find "$REF_DIR" -regex ".*.lock" | while read -r filepath; do | ||||||
|  |         rm -r "$filepath" | ||||||
|  |     done | ||||||
|  | 
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| main "$@" | main "$@" | ||||||
|  | @ -313,7 +388,7 @@ func buildInitBashScript(jenkins *v1alpha2.Jenkins) (*string, error) { | ||||||
| 		InitConfigurationPath:    jenkinsInitConfigurationVolumePath, | 		InitConfigurationPath:    jenkinsInitConfigurationVolumePath, | ||||||
| 		BasePlugins:              jenkins.Spec.Master.BasePlugins, | 		BasePlugins:              jenkins.Spec.Master.BasePlugins, | ||||||
| 		UserPlugins:              jenkins.Spec.Master.Plugins, | 		UserPlugins:              jenkins.Spec.Master.Plugins, | ||||||
| 		InstallPluginsCommand:    installPluginsCommand, | 		InstallPluginsCommand:    JenkinsScriptsVolumePath + "/" + installPluginsCommand, | ||||||
| 		JenkinsScriptsVolumePath: JenkinsScriptsVolumePath, | 		JenkinsScriptsVolumePath: JenkinsScriptsVolumePath, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -343,7 +418,7 @@ func NewScriptsConfigMap(meta metav1.ObjectMeta, jenkins *v1alpha2.Jenkins) (*co | ||||||
| 		ObjectMeta: meta, | 		ObjectMeta: meta, | ||||||
| 		Data: map[string]string{ | 		Data: map[string]string{ | ||||||
| 			InitScriptName:        *initBashScript, | 			InitScriptName:        *initBashScript, | ||||||
| 			installPluginsCommand: fmt.Sprintf(installPluginsBashFmt, getJenkinsHomePath(jenkins)), | 			installPluginsCommand: installPluginsBashScript, | ||||||
| 		}, | 		}, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue