%{lua: -- This RPM spec file can build -- -- ## Usage -- -- ### pup_module_info_dir -- -- When you build, you must define the macro 'pup_module_info_dir' so that rpm -- knows where to find preliminary information. -- -- If 'pup_module_info_dir' isn't defined or available, rpm will look in -- '_sourcedir' for the files, falling back to the current directory as a last -- resort. -- -- Example: -- -- rpmbuild -D 'pup_module_info_dir /home/user/project/puppet_module' -ba SPECS/specfile.spec -- -- ### relevant files -- -- 'pup_module_info_dir' should be a directory that contains the following items: -- -- * 'metadata.json' <- REQUIRED file that must contain the -- following metadata: -- - 'name' - package name -- - 'version' - package version -- - 'license' - package license -- - 'summary' - package summary -- - 'source' - package source -- * 'build/rpm_metadata/requires' <- optional list of 'Requires', -- 'Provides', and 'Obsoletes' to -- supplement those auto-generated in -- this spec file. -- * 'build/rpm_metadata/release' <- optional RPM release number to use in -- lieu of the number '0' hard-coded in -- this spec file. -- * 'CHANGELOG' <- optional RPM-formatted CHANGELOG to -- use in lieu of the minimal changelog -- entry auto-generated in this file. -- * 'build/rpm_metadata/custom/' <- optional directory to place files to -- add custom scriptlets and triggers. -- -- local LUA_DEBUG = ((rpm.expand('%{lua_debug}') or '0') == '1') -- Print debugging info to STDERR (if LUA_DEBUG is true) function lua_stderr( msg ) if LUA_DEBUG then -- io.stderr:write(tostring(msg):gsub("%f[^%z\n]","LUA #stderr#: ")) -- io.stderr:write(tostring(msg)) io.stderr:write(msg) end end local function get_src_dir() local src_dir = rpm.expand('%{pup_module_info_dir}') if src_dir:match('^%%') or (posix.stat(src_dir, 'type') ~= 'directory') then lua_stderr("WARNING: -D pup_module_info_dir ("..tostring(src_dir)..") could not be used!\n") lua_stderr(" falling back to src_dir = _sourcedir\n") -- FIXME?: rpmlint considers the use of _sourcedir to be an Error: -- (see: https://fedoraproject.org/wiki/Packaging:RPM_Source_Dir) src_dir = rpm.expand('%{_sourcedir}') if (posix.stat((src_dir .. "/metadata.json"), 'type') ~= 'regular') then lua_stderr("WARNING: couldn't find metadata.json in '"..tostring(src_dir).."'!\n") lua_stderr(" falling back to src_dir = posix.getcwd() ("..posix.getcwd()..")\n") src_dir = posix.getcwd() end end return src_dir end -- path to project directory / source files src_dir = get_src_dir() -- directory to look for customizations (e.g., scriptlets, triggers) custom_content_dir = src_dir .. "/build/rpm_metadata/custom/" -- list of custom content to inject into the spec file custom_content_table = {} -- list of scriptlets/triggers that have been declared (to avoid duplicates) declared_scriptlets_table = {} -- patterns to recognize scriptlet and trigger declarations -- -- NOTE: Lua patterns are not regexes , and do not support alternation. -- So, we try to stay efficient by iterating through as few patterns as -- possible by short-ciruiting several matches. -- (e.g. '^%%pre' matches both '%pre' and '%pretrans') -- SCRIPTLET_PATTERNS = { '^%%pre', '^%%post', '^%%trigger' } -- These UNKNOWN entries should break the build if something bad happens package_name = "UNKNOWN" package_version = "UNKNOWN" module_license = "UNKNOWN" -- Default to 1 package_release = 1 lua_stderr("\n") lua_stderr("--------------------------------------------------------------------------------\n") lua_stderr("RPM/LUA build environment:\n") lua_stderr("------:\n") lua_stderr("LUA _VERSION = '".._VERSION.."'\n") lua_stderr("posix.getcwd() = '"..posix.getcwd().."'\n") lua_stderr("\n") lua_stderr("macros:\n") lua_stderr("------:\n") lua_stderr("'%{pup_module_info_dir}' = '"..rpm.expand('%{pup_module_info_dir}').."'\n") lua_stderr("_specdir = '"..rpm.expand('%{_specdir}').."'\n") lua_stderr("_buildrootdir = '"..rpm.expand('%{_buildrootdir}').."'\n") lua_stderr("buildroot = '"..rpm.expand('%{buildroot}').."'\n") lua_stderr("RPM_BUILD_ROOT = '"..rpm.expand('%{RPM_BUILD_ROOT}').."'\n") lua_stderr("\n") lua_stderr("local variables:\n") lua_stderr("------:\n") lua_stderr("src_dir = '".. src_dir .."'\n") lua_stderr("custom_content_dir = '"..custom_content_dir.."'\n# ---\n") lua_stderr("--------------------------------------------------------------------------------\n") lua_stderr("\n") -- Pull the Relevant Metadata out of the Puppet module metadata.json. function read_metadata(src_dir) local metadata = '' local metadata_file = src_dir .. "/metadata.json" local metadata_fh = io.open(metadata_file,'r') if metadata_fh then metadata = metadata_fh:read("*all") -- Ignore the first curly brace metadata = metadata:gsub("{}?", '|', 1) -- Ignore all keys that are below the first level metadata = metadata:gsub("{.-}", '') metadata = metadata:gsub("%[.-%]", '') else error("Could not open 'metadata.json': ".. metadata_file, 0) end return metadata end metadata = read_metadata(src_dir) -- This starts as an empty string so that we can build it later module_requires = '' } %{lua: -- Get the Module Name and put it in the correct format local name_match = metadata:match('"name":%s+"(.-)"%s*,') module_author = '' module_name = '' if name_match then package_name = ('pupmod-' .. name_match) local i = 0 for str in name_match:gmatch('[^-]+') do if i == 0 then module_author = str else if module_name == '' then module_name = str else module_name = (module_name .. '-' .. str) end end i = i+1 end else error("Could not find valid package name in 'metadata.json'", 0) end } %{lua: -- Get the Module Version -- Note on '-rc0' style Release versions in metadata.json: -- Some Puppet orgs (like voxpupuli) append '-rc0' to the module version -- to denote prerelease status and act as a safety check (because the Forge -- only accepts release in SemVer \d+\.\d+\.\d+ release format) -- -- This code will remove a '-rc0' style prerelease string from the RPM -- Version, and use it as the default RPM Release (instead of '-1') local version_match = metadata:match('"version":%s+"(.-)"%s*,'):gsub('-.*','') if version_match then package_version = version_match else error("Could not find valid package version in 'metadata.json'", 0) end -- Get the Module Release (if present) local release_match = metadata:match('"version":%s+"[%d.]+-([^"]+)"') if release_match then package_release = release_match end } %{lua: -- Get the Module License local license_match = metadata:match('"license":%s+"(.-)"%s*,') if license_match then module_license = license_match else error("Could not find valid package license in 'metadata.json'", 0) end } %{lua: -- Get the Module Summary local summary_match = metadata:match('"summary":%s+"(.-)"%s*,') if summary_match then module_summary = summary_match else error("Could not find valid package summary in 'metadata.json'", 0) end } %{lua: -- Get the Module Source line for the URL string local source_match = metadata:match('"source":%s+"(.-)"%s*,') if source_match then module_source = source_match else error("Could not find valid package source in 'metadata.json'", 0) end } %{lua: -- Snag the RPM-specific items out of the 'build/rpm_metadata' directory -- First, the Release Number rel_file = io.open(src_dir .. "/build/rpm_metadata/release", "r") if not rel_file then -- Need this for the SRPM case rel_file = io.open(src_dir .. "/release", "r") end if rel_file then for line in rel_file:lines() do is_comment = line:match("^%s*#") is_blank = line:match("^%s*$") if not (is_comment or is_blank) then package_release = line break end end end } %{lua: -- Next, the Requirements req_file = io.open(src_dir .. "/build/rpm_metadata/requires", "r") if not req_file then -- Need this for the SRPM case req_file = io.open(src_dir .. "/requires", "r") end if req_file then for line in req_file:lines() do valid_line = (line:match("^Requires: ") or line:match("^Obsoletes: ") or line:match("^Provides: ") or line:match("^Recommends: ")) if valid_line then module_requires = (module_requires .. "\n" .. line) end end end } %global _binaries_in_noarch_packages_terminate_build 0 %define module_name %{lua: print(module_name)} %define package_name %{lua: print(package_name)} Summary: %{module_name} Puppet Module Name: %{package_name} Version: %{lua: print(package_version)} Release: %{lua: print(package_release)}%{?dist} License: %{lua: print(module_license)} Group: Applications/System Source0: %{package_name}-%{version}-%{release}.tar.gz Source1: %{lua: print("metadata.json")} %{lua: -- Include our sources as appropriate changelog = io.open(src_dir .. "/CHANGELOG","r") if changelog then print("Source2: " .. "CHANGELOG\n") end if rel_file then print("Source3: " .. "release\n") end if req_file then print("Source4: " .. "requires\n") end } URL: %{lua: print(module_source)} BuildRoot: %{_tmppath}/%{package_name}-%{version}-%{release}-buildroot BuildArch: noarch Requires(pre): simp-adapter >= 0.1.1 Requires(preun): simp-adapter >= 0.1.1 Requires(preun): simp-adapter >= 0.1.1 Requires(posttrans): simp-adapter >= 0.1.1 %{lua: print(module_requires)} Provides: pupmod-%{lua: print(module_name)} = %{lua: print(package_version .. "-" .. package_release)} Obsoletes: pupmod-%{lua: print(module_name)} < %{lua: print(package_version .. "-" .. package_release)} %{lua: -- This is a workaround for the 'simp-rsync' real RPM conflict but is -- required by some external modules. -- This should be removed when SIMP 6 is stable author_rpm_name = module_author .. "-" .. module_name if author_rpm_name ~= 'simp-rsync' then print("Provides: " .. author_rpm_name .. " = " .. package_version .. "-" .. package_release .. "\n") print("Obsoletes: " .. author_rpm_name .. " < " .. package_version .. "-" .. package_release .. "\n") end } Prefix: /usr/share/simp/modules %description %{lua: print(module_summary)} %prep %setup -q -n %{package_name}-%{version} %build %install [ "%{buildroot}" != "/" ] && rm -rf %{buildroot} mkdir -p %{buildroot}/%{prefix} curdir=`pwd` dirname=`basename $curdir` cp -r ../$dirname %{buildroot}/%{prefix}/%{module_name} # Modules should *never* contain symlinks find %{buildroot} -type l -delete # Remove unnecessary assets rm -rf %{buildroot}/%{prefix}/%{module_name}/.git rm -f %{buildroot}/%{prefix}/%{module_name}/*.lock rm -rf %{buildroot}/%{prefix}/%{module_name}/spec/fixtures/modules rm -rf %{buildroot}/%{prefix}/%{module_name}/dist rm -rf %{buildroot}/%{prefix}/%{module_name}/junit rm -rf %{buildroot}/%{prefix}/%{module_name}/log %clean [ "%{buildroot}" != "/" ] && rm -rf %{buildroot} mkdir -p %{buildroot}/%{prefix} %{lua: -- returns true if 'scriptlet_name' has already been declared function is_scriplet_declared(scriptlet_name, declared_scriptlets_table) for _,name in ipairs(declared_scriptlets_table) do if (name == scriptlet_name) then return true end end return false end -- returns true if 'line' is a scriptlet or trigger header function is_valid_scriptlet_header(line) local match = false for _, patt in ipairs(SCRIPTLET_PATTERNS) do if line:match(patt) then match = true break end end return match end -- -- adds content to the custom_content_table -- function define_custom_content( content, custom_content_table, declared_scriptlets_table ) lua_stderr("# evaluating extra content: \n".. (content:gsub("%f[^%z\n]"," | ")) .."\n") if content then local _content = '' local recording = true for line in content:gmatch("([^\n]*)\n?") do -- skip duplicate scriptlets if is_valid_scriptlet_header(line) then local _line = line:gsub("^%s+",""):gsub("%s+$","") if is_scriplet_declared(_line, declared_scriptlets_table) then lua_stderr("WARNING: scriptlet '".._line.. "' has already been declared (skipping scriptlet).\n") recording = false else lua_stderr('+ "'.._line..'" is recognized as a scriptlet/trigger.\n') recording = true table.insert(declared_scriptlets_table, _line) end end if recording then _content = _content .. line .. "\n" else lua_stderr(" skipping line '"..line.."'\n") end end table.insert(custom_content_table, _content ) end end function load_custom_content_files(custom_content_dir, custom_content_table, declared_scriptlets_table) if (posix.stat(custom_content_dir, 'type') == 'directory') then for i,basename in pairs(posix.dir(custom_content_dir)) do local file = custom_content_dir .. basename -- only accept files that are not dot files (".filename") if (basename:match('^[^%.]') and (posix.stat(file, 'type') == 'regular')) then lua_stderr("INFO: found custom RPM spec file snippet: '" .. file .. "'\n") local file_handle = io.open(file,'r') if file_handle then local _content = file_handle:read("*all") define_custom_content(_content, custom_content_table, declared_scriptlets_table) else lua_stderr("WARNING: could not read '"..file.."'\n") end file_handle:close() else lua_stderr("WARNING: skipping invalid filename '"..basename.."'\n") end end else lua_stderr("WARNING: not found: " .. custom_content_dir .. "\n") end end -- Declares default scriptlets for SIMP 6.X (referenced from 6.1.0) -- -- In order to keep the package-maintained pupmod-*-* packages. -- Packages notify /usr/local/sbin/simp_rpm_helper. -- See: https://github.com/simp/simp-adapter/blob/master/src/sbin/simp_rpm_helper -- -- This function should be called last -- function declare_default_scriptlets(custom_content_table, declared_scriptlets_table) local marker_dir = rpm.expand('%{_localstatedir}/lib/rpm-state/simp-adapter') local marker_file = marker_dir..'/rpm_status$1.'..module_name local pre_comment = ( '# when $1 = 1, this is an install\n'.. '# when $1 = 2, this is an upgrade\n' ) local preun_comment = ( '# when $1 = 1, this is the uninstall of the previous version during an upgrade\n'.. '# when $1 = 0, this is the uninstall of the only version during an erase\n' ) local postun_comment = ( '# when $1 = 1, this is the uninstall of the previous version during an upgrade\n'.. '# when $1 = 0, this is the uninstall of the only version during an erase\n' ) local DEFAULT_SCRIPTLETS = { ['pre'] = {comment = pre_comment, custom='mkdir -p '..marker_dir..'\ntouch '..marker_file..'\n'}, ['preun'] = {comment = preun_comment, custom=''}, ['postun'] = {comment = postun_comment, custom=''} } local rpm_dir = rpm.expand('%{prefix}/' .. module_name) for name,data in pairs(DEFAULT_SCRIPTLETS) do local content = ('%'..name.."\n".. '# (default scriptlet for SIMP 6.x)\n'.. data.comment .. data.custom .. 'if [ -x /usr/local/sbin/simp_rpm_helper ] ; then\n'.. ' /usr/local/sbin/simp_rpm_helper --rpm_dir='.. rpm_dir.." --rpm_section='"..name.."' --rpm_status=$1\n".. 'fi\n\n' ) define_custom_content(content, custom_content_table, declared_scriptlets_table) end local install_marker_file = marker_dir..'/rpm_status1.'..module_name local upgrade_marker_file = marker_dir..'/rpm_status2.'..module_name local posttrans_content = ('%posttrans\n'.. '# (default scriptlet for SIMP 6.x)\n'.. '# Marker file is created in %pre and only exists for installs or upgrades\n'.. "# when marker file is prepended with 'rpm_status1.', this is an install\n".. "# when marker file is prepended with 'rpm_status2.', this is an upgrade\n".. 'if [ -e '..install_marker_file..' ] ; then\n'.. ' rm '..install_marker_file..'\n'.. ' if [ -x /usr/local/sbin/simp_rpm_helper ] ; then\n'.. ' /usr/local/sbin/simp_rpm_helper --rpm_dir='.. rpm_dir.." --rpm_section='posttrans' --rpm_status=1\n".. ' fi\n'.. 'elif [ -e '..upgrade_marker_file..' ] ; then\n'.. ' rm '..upgrade_marker_file..'\n'.. ' if [ -x /usr/local/sbin/simp_rpm_helper ] ; then\n'.. ' /usr/local/sbin/simp_rpm_helper --rpm_dir='.. rpm_dir.." --rpm_section='posttrans' --rpm_status=2\n".. ' fi\n'.. 'fi\n\n' ) define_custom_content(posttrans_content, custom_content_table, declared_scriptlets_table) end -- insert custom content (e.g., rpm_metadata/custom/*, scriptlets) function print_extra_content( custom_content_table ) local extra_content = table.concat(custom_content_table, "\n") .. "\n" lua_stderr("\n========== DYNAMIC CONTENT SUMMARY ========== (begin)\n" .. rpm.expand( extra_content ) .. "\n========== DYNAMIC CONTENT SUMMARY ========== (end)\n") print(extra_content) end load_custom_content_files( custom_content_dir, custom_content_table, declared_scriptlets_table ) declare_default_scriptlets(custom_content_table, declared_scriptlets_table) print_extra_content(custom_content_table) } %files %defattr(0640,root,root,0750) %{prefix}/%{module_name} %changelog %{lua: -- Finally, the CHANGELOG -- A default CHANGELOG in case we cannot find a real one default_changelog = [===[ * $date Auto Changelog - $version-$release - Latest release of $name ]===] default_lookup_table = { date = os.date("%a %b %d %Y"), version = package_version, release = package_release, name = package_name } changelog = io.open(src_dir .. "/CHANGELOG","r") if changelog then first_line = changelog:read() if first_line:match("^*%s+%a%a%a%s+%a%a%a%s+%d%d?%s+%d%d%d%d%s+.+") then changelog:seek("set",0) print(changelog:read("*all")) else print((default_changelog:gsub('$(%w+)', default_lookup_table))) end else print((default_changelog:gsub('$(%w+)', default_lookup_table))) end }