modmanage.in 12.4 KB
Newer Older
1
#!@PMODULES_HOME@/bin/bash
2

3
shopt -s expand_aliases
4

5 6
declare -r bindir=$(cd $(dirname "$0") && pwd)
declare -r libdir=$(cd "${bindir}/../lib" && pwd)
7

8
source "${libdir}/libpmodules.bash"
9 10 11 12 13 14

print_version() {
	echo "
Pmodules ${version} using Tcl Environment Modules @MODULES_VERSION@
Copyright GNU GPL v2
" 1>&2
15 16
}

17 18 19 20 21 22 23 24 25 26 27 28 29
usage() {
	local -r prog=$(basename $0)
	print_version
	echo "
Usage: ${prog} [ switches ] [ subcommand ] [subcommand-args ]

Switches:
        --dry-run     do nothing
        --force       force overwrite

Available SubCommands and Args:
        init [--src=<src>] [--user=<user>] --dst=<dst>
                Initialize a new minimal Pmodule environment.
30 31 32

        install <module> [--with=<dep>...]
                Install matching modules
33 34 35

        sync [--delete] [--dst=<dst>] <src>
                Synchronize modules.
36 37
"
}
38

39 40

declare force='no'
41 42
declare dry_run='no'
declare DRY=''
43 44
declare subcommand=''
declare sargs=()
45

46 47 48 49 50 51 52 53
subcommand_help_init() {
	echo "
init [--src=<src>] [--user=<user>] <dst>
                Initialize a new minimal Pmodule environment in directory
                <dst>. The <user> parameter must only be present if
                 ${prog} is executed as root.
" 1>&2
}
54

55 56 57 58 59 60 61
subcommand_help_install() {
	echo "
install <module> [--with=<dep>...]
                Install matching modules
" 1>&2
}

62 63 64 65 66 67 68 69 70 71 72
subcommand_help_sync() {
	echo "
sync [--delete] [--dst=<dst>] <src>
                Synchronize environment modules and configuration files
                from Pmodule environment <src> to Pmodule environment <dst>
                (default: currently active Pmodule environment).
                If --delete is given, unmarked modules present in <dst>
                will be deleted.
" 1>&2
}

73 74 75 76 77 78 79 80 81
subcommand_help() {
	if [[ $# == 0 ]]; then
	        usage
	elif typeset -F subcommand_help_$1 > /dev/null 2>&1 ; then
	        # help for sub-command
		subcommand_help_$1
	else
		usage
	fi
82 83
}

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
#
# Derive the relative module installation path
# from the relative modulefile path
#
# $1: relative module file path
#
get_module_prefix() {
	local -a comp=( ${1//\// } )    # split rel.path into components
	local path="${comp[0]}"		# result path
	local -i i
	for ((i=1; i<${#comp[@]}; i+=2)); do
		path+="/${comp[$((-i-1))]}/${comp[$((-i))]}"
	done
	echo "${path}"
}

#
# Derive the relative module release file path
# from the relative module file path
#
# $1: relative module file path
#
get_releasefile_name() {
	echo "$(dirname "$1")/.release-$(basename "$1")"
}

#
# Sync a module from one Pmodules environment to another:
# - sync module installation
# - sync modulefile
# - sync release file
#
# $1: relative modulefile path
# $2: source prefix of Pmodule environment
# $3: target prefix of Pmodule environment
#
sync_module() {
	local -r rel_modulefile=$1
	local -r src_prefix=$2
	local -r target_prefix=$3

	local -r rel_module_prefix=$( get_module_prefix "${rel_modulefile}" )
	local -r rel_releasefile=$( get_releasefile_name "${rel_modulefile}" )

128
	$DRY mkdir -p "${target_prefix}/${rel_module_prefix}" || return $?
129 130 131
	$DRY rsync --links --perms --recursive --delete \
	      "${src_prefix}/${rel_module_prefix}/" \
	      "${target_prefix}/${rel_module_prefix}/" || return $?
132 133 134 135 136 137 138 139

	local -r src_modulefile="${src_prefix}/${PSI_MODULES_ROOT}/${rel_modulefile}"
	local -r src_releasefile="${src_prefix}/${PSI_MODULES_ROOT}/${rel_releasefile}"
	local -r target_modulefile="${target_prefix}/${PSI_MODULES_ROOT}/${rel_modulefile}"
	local -r target_releasefile="${target_prefix}/${PSI_MODULES_ROOT}/${rel_releasefile}"

	if [[ -e "${src_modulefile}" ]] || [[ -e "${src_releasefile}" ]]; then
	        local dir=$( dirname "${target_modulefile}" )
140
		$DRY mkdir -p "${dir}" || return $?
141 142 143 144 145 146 147 148 149
	fi
	if [[ -e "${src_modulefile}" ]]; then
	        $DRY rsync --links --perms --recursive \
		     "${src_modulefile}" "${target_modulefile}" || return $?
	fi
	if [[ -e "${src_releasefile}" ]]; then
	        $DRY rsync --links --perms --recursive \
		     "${src_releasefile}" "${target_releasefile}" || return $?
	fi
150 151
}

152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
#
# Sync the Pmodules configuration and templates
#
# $1: source prefix of Pmodule environment
# $2: target prefix of Pmodule environment
#
sync_config() {
	src="$1/${PSI_CONFIG_DIR}/"
	dst="$2/${PSI_CONFIG_DIR}/"
	$DRY rsync --recursive --links --perms --delete \
		"${src}" "${dst}" || die 1 "Error: synch operation failed!"
	echo

	src="$1/${PSI_TEMPLATES_DIR}/"
	dst="$2/${PSI_TEMPLATES_DIR}/"
	$DRY rsync --recursive --links --perms --delete \
		"${src}" "${dst}" || die 1 "Error: synch operation failed!"
	echo
}

172 173
subcommand_init() {
	local src=''
174
	local target_prefixes=()
175 176 177 178 179 180 181 182 183
	local user=''
	local opts=''
	opts=$(get_options -o h -l src: -l user: -l help -- "$@")
	if [[ $? != 0 ]]; then
		subcommand_help_init
		exit 1
	fi
	eval set --  "${opts}"
	while (($# > 0)); do
184
		case $1 in
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
			--src )
				src=$2
				shift
				;;
			--user )
				user=$2
				shift
				;;
			-- )
				:
				;;
			-* | -h | --help )
				echo "$1: illegal option" 1>&2
				subcommand_help_init
				exit 1
				;;
			* )
202
				target_prefixes+=( "$1" )
203 204 205 206
				;;
		esac
		shift
	done
207
	(( ${#target_prefixes[@]} != 0 )) || die 1 "Error: no target directory specified!"
208

209 210
	if [[ -n "${src}" ]]; then
		[[ -d "${src}" ]] || die 1 "Error: ${src}: source directory does not exist!"
211 212
		[[ -r "${src}/config/profile.bash" ]] || \
		    die 1 "Error: ${src}: shell profile does not exist or is not readable!"
213 214 215 216
		source "${src}/config/profile.bash"
	fi
	local -i euid=$(id -u)
	if (( euid == 0 )); then
217
	        [[ -n "${user}" ]] || \
218
		    die 1 "Error: --user parameter is required!"
219 220
		id -u "${user}" > /dev/null 2>&1 || \
		    die 1 "Error: Unable to retrieve user id of user '${user}'"
221
	else
222
		[[ -z "${user}" ]] || \
223
		    die 1 "Error: --user option is only allowed if running as root!"
224
	fi
225 226 227

	check_pmodules_env || die 1 "Giving up..."

228
	echo "
229 230 231 232
Attempting to create a minimal module environment from the
environment at '${PSI_PREFIX}'
"

233 234
	init_pmodules_environment() {
		local -r target_prefix=$1
235 236
		local src=''
		local dst=''
237 238 239 240
		echo "Initializing target directory '${target_prefix}' ..."
		echo
		if [[ -d "${target_prefix}" ]]  && [[ ${force} == no ]]; then
			echo "Warning: ${target_prefix} already exists."
241
			get_YN_answer "Do you really want to re-run the initialization? (y/N) " || \
242
				      die 1 "Abort ..."
243
		fi
244

245 246
		echo "Creating target directory '${target_prefix}'..."
		$DRY mkdir -p "${target_prefix}" || die 1 "Error: make directory failed!"
247
		echo
248

249 250 251
		echo "Syncing configuration ..."
		sync_config "${PSI_PREFIX}" \
		            "${target_prefix}" || die 1 "Error: configuration synchronization failed!"
252 253 254 255 256

		echo "Syncing Pmodules ..."
		sync_module "Tools/Pmodules/${PMODULES_VERSION}" \
			    "${PSI_PREFIX}" \
			    "${target_prefix}" || die 1 "Error: sync Pmodules failed!"
257
		echo
258

259 260 261 262
		if [[ -n "${user}" ]]; then
	                echo "Changing user of new module environment to '${user}'..."
                        $DRY chown -R "${user}" "${target_prefix}" || die 1 "Error: changing owner failed!"
                        echo
263
		fi
264
		echo "New minimal module environment created at '${target_prefix}'."
265
		echo "To use this environment, execute"
266 267
		echo "   sudo ln -fs ${target_prefix} /opt/psi"
		echo "   source /opt/psi/${PSI_CONFIG_DIR}/profile.bash"
268 269 270
	}

	umask 022
271 272
	for target_prefix in "${target_prefixes[@]}"; do
		init_pmodules_environment "${target_prefix}"
273 274
	done

275 276
}

277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
subcommand_install() {
	local opts=''
	local -a with=()
	local -a releases=()
	local -a module_pattern=()

	opts=$(get_options -o h -l with: -l release: -l help -- "$@")
	if [[ $? != 0 ]]; then
		subcommand_help_install
		exit 1
	fi
	eval set --  "${opts}"
	while (($# > 0)); do
		case $1 in
			--release )
				releases+=( "$2" )
				echo $2
				shift
				;;
			--with )
				with+=( "$2" )
				shift
				;;
			-- )
				:
				;;
			-* | -h | --help )
				echo "$1: illegal option" 1>&2
				subcommand_help_init
				exit 1
				;;
			* )
				module_pattern+=( "$1" )
				;;
		esac
		shift
	done
	${PMODULES_HOME}/bin/modulecmd bash search "${module_pattern[@]}" "${with[@]/#/--with}" "${releases[@]/#/--release=}" --no-header
}

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431
subcommand_sync() {
	local delete=false
	local opts=''
	local dst_prefix=''
	local source_prefix=''
	opts=$(get_options -o h -l dst: -l delete -l help -- "$@")
	if [[ $? != 0 ]]; then
		subcommand_help_sync
		exit 1
	fi
	eval set --  "${opts}"
	while (($# > 0)); do
		case $1 in
			--dst )
				dst_prefix="$2"
				shift
				;;
			--delete )
				delete=true
				;;
			-- )
				:
				;;
			-* | -h | --help )
				echo "$1: illegal option" 1>&2
				subcommand_help_init
				exit 1
				;;
			* )
				[[ -n "${source_prefix}" ]] && die 1 "Error: Only one source is allowed!"
				source_prefix="$1"
				;;
		esac
		shift
	done
	unset -v opts
	if [[ -z "${dst_prefix}" ]]; then
		[[ -z "${PSI_PREFIX}" ]] && die 1 "Error: No current module environment and no destination specified!"
		dst_prefix="${PSI_PREFIX}"
	fi
	(
		PSI_PREFIX="${dst_prefix}" check_pmodules_env || die 1 "Error: invalid destination modules environment!"
	) || die 1 "Giving up..."
	(
		PSI_PREFIX="${src_prefix}" check_pmodules_env || die 1 "Error: invalid source modules environment!"
	) || die 1 "Giving up..."
	[[ "$( cd "$src_prefix"; pwd -P )" == "$( cd "$dst_prefix"; pwd -P )" ]] && die 1 "Error: source and destination are equal!"
	local modbin=${PMODULES_HOME#"${PSI_PREFIX}/"}/bin/modulecmd.tcl
	local file_type_src=$( file -b "${src_prefix}/${modbin}" 2>&1 || echo err1 )
	local file_type_dst=$( file -b "${dst_prefix}/${modbin}" 2>&1 || echo err2 )
	[[ "${file_type_src}" == "${file_type_dst}" ]] || die 1 "Error: The file signatures in the source and destination installation do not match!"
	unset -v file_type_src file_type_dst

	local profile_script="${src_prefix}/${PSI_CONFIG_DIR}/profile.bash"
	[[ -r "${profile_script}" ]] || die 1 "Error: Unable to find profile script of installation $profile_script";
	local search_script="${src_prefix}/${modbin}/bin/modulecmd"
	[[ -x "${search_script}" ]] || die 1 "Error: Unable to find search script of installation $search_script";
#	local dialog_script="${src_prefix}/${modbin}/bin/dialog.bash"
	local dialog_script="./dialog.bash"
	[[ -r "$dialog_script" ]] || die 1 "Error: Unable to find dialog script of installation $dialog_script";

	source "$profile_script"    # set variables for the source installation
	DIALOG_LIB=1                # use dialog script as a library
	source "$dialog_script"     # dialog functions

	local -a selected_modules

	# Redefine module_out to append modules to the selected_modules variable
	function module_out() {
		local -a args=(${modlist[$1]})
		local path=""
		IFS=/
		[[ -n "${args[3]}" ]] && path="/${args[*]:3}"
		unset IFS
		selected_modules+=( "${args[2]}${path}/${args[0]}" )
	}

	module_picker "${dst_prefix}" < <("$search_script" bash search --no-header -a 2>&1)   # this calls module_out for each selected module, filling up the selected_modules array

	local -a destination_modules=( $(cd "${dst_prefix}/${PSI_MODULES_ROOT}"; find -L . -type f | while read f; do echo ${f#./}; done) )

	# redefine set difference, the version in dialog.bash only handles integers
	function set_difference() {  #  $1 \ $2
		local -a operand1=($1)
		local -a operand2=($2)
		local -A members
		local elem
		for elem in "${operand1[@]}"; do
			members[$elem]=1
		done
		for elem in "${operand2[@]}"; do
			unset members[$elem]
		done
		echo ${!members[@]}
	}

	if [[ "$delete" == "true" ]]; then
		local -a modules_delete=( $(set_difference "${destination_modules[*]}" "${selected_modules[*]}") )
		for m in "${modules_delete[@]}"; do
			delete_module "$m" "$dst_prefix"
		done
		unset modules_delete
	fi

	local -a modules_copy=( $(set_difference "${selected_modules[*]}" "${destination_modules[*]}") )
	if [[ -n $modules_copy ]]; then
		echo "Syncing configuration ..."
		sync_config "$src_prefix" "$dst_prefix" || die 1 "Error: syncing the configuration failed"
        fi
        for m in "${modules_copy[@]}"; do
		sync_module "$m" "$src_prefix" "$dst_prefix" || die 1 "Error: syncing of module $m failed!"
        done
	unset modules_copy
}

432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
while (($# > 0)); do
	case $1 in
		-h | -H | -\? | --help | -help )
			usage
			exit 1
			;;
		-V | --version )
			print_version
			exit 1
			;;
		-f | --force )
			force='yes'
			;;
		--dry-run )
			dry_run='yes'
			DRY='echo'
			;;
		-* )
			echo "$1: unknown switch.\n" 1>&2
			exit 1
			;;
453
		init|install|sync|help )
454 455 456 457 458 459 460 461 462 463
			subcommand="subcommand_$1"
			shift
			sargs=( $* )
			shift $#
			;;
		* )
			echo "$1: unknown sub-command" 1>&2
			exit 1
	esac
	shift
464
done
465 466 467 468 469 470 471 472

$subcommand "${sargs[@]}"

# Local Variables:
# mode: sh
# sh-basic-offset: 8
# tab-width: 8
# End: