@@ -267,6 +267,20 @@
projects info <PROJECT,...> Show details about the listed projects.
+sources list
+ List the available sources
+
+sources info <SOURCE-NAME,...>
+ Details about the source.
+
+sources add <SOURCE.TOML>
+ Add a package source to the server.
+
+sources change <SOURCE.TOML>
+ Change an existing source
+
+sources delete <SOURCE-NAME>
+ Delete a package source."""
+#
+# Copyright (C) 2018 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+importlogging
+log=logging.getLogger("composer-cli")
+
+importos
+importjson
+
+fromcomposerimporthttp_clientasclient
+fromcomposer.cli.utilitiesimportargify,handle_api_result
+
+
[docs]defsources_list(socket_path,api_version,args,show_json=False):
+ """Output the list of available sources
+
+ :param socket_path: Path to the Unix socket to use for API communication
+ :type socket_path: str
+ :param api_version: Version of the API to talk to. eg. "0"
+ :type api_version: str
+ :param args: List of remaining arguments from the cmdline
+ :type args: list of str
+ :param show_json: Set to True to show the JSON output instead of the human readable output
+ :type show_json: bool
+
+ sources list
+ """
+ api_route=client.api_url(api_version,"/projects/source/list")
+ result=client.get_url_json(socket_path,api_route)
+ ifshow_json:
+ print(json.dumps(result,indent=4))
+ return0
+
+ print("Sources: %s"%", ".join(result["sources"]))
+ return0
+
+
[docs]defsources_info(socket_path,api_version,args,show_json=False):
+ """Output info on a list of projects
+
+ :param socket_path: Path to the Unix socket to use for API communication
+ :type socket_path: str
+ :param api_version: Version of the API to talk to. eg. "0"
+ :type api_version: str
+ :param args: List of remaining arguments from the cmdline
+ :type args: list of str
+ :param show_json: Set to True to show the JSON output instead of the human readable output
+ :type show_json: bool
+
+ sources info <source-name>
+ """
+ iflen(args)==0:
+ log.error("sources info is missing the name of the source")
+ return1
+
+ ifshow_json:
+ api_route=client.api_url(api_version,"/projects/source/info/%s"%",".join(args))
+ result=client.get_url_json(socket_path,api_route)
+ print(json.dumps(result,indent=4))
+ return0
+ else:
+ api_route=client.api_url(api_version,"/projects/source/info/%s?format=toml"%",".join(args))
+ result=client.get_url_raw(socket_path,api_route)
+ print(result)
+ return0
+
+
[docs]defsources_add(socket_path,api_version,args,show_json=False):
+ """Add or change a source
+
+ :param socket_path: Path to the Unix socket to use for API communication
+ :type socket_path: str
+ :param api_version: Version of the API to talk to. eg. "0"
+ :type api_version: str
+ :param args: List of remaining arguments from the cmdline
+ :type args: list of str
+ :param show_json: Set to True to show the JSON output instead of the human readable output
+ :type show_json: bool
+
+ sources add <source.toml>
+ """
+ api_route=client.api_url(api_version,"/projects/source/new")
+ rval=0
+ forsourceinargify(args):
+ ifnotos.path.exists(source):
+ log.error("Missing source file: %s",source)
+ continue
+ source_toml=open(source,"r").read()
+
+ result=client.post_url_toml(socket_path,api_route,source_toml)
+ ifhandle_api_result(result,show_json):
+ rval=1
+ returnrval
+
+
[docs]defsources_delete(socket_path,api_version,args,show_json=False):
+ """Delete a source
+
+ :param socket_path: Path to the Unix socket to use for API communication
+ :type socket_path: str
+ :param api_version: Version of the API to talk to. eg. "0"
+ :type api_version: str
+ :param args: List of remaining arguments from the cmdline
+ :type args: list of str
+ :param show_json: Set to True to show the JSON output instead of the human readable output
+ :type show_json: bool
+
+ sources delete <source-name>
+ """
+ api_route=client.api_url(api_version,"/projects/source/delete/%s"%args[0])
+ result=client.delete_url_json(socket_path,api_route)
+
+ returnhandle_api_result(result,show_json)
+def_depsolve(dbo,projects):
+ """Add projects to a new transaction
-
[docs]defprojects_depsolve(dbo,project_names):
+ :param dbo: dnf base object
+ :type dbo: dnf.Base
+ :param projects: The projects and version globs to find the dependencies for
+ :type projects: List of tuples
+ :returns: None
+ :rtype: None
+ :raises: ProjectsError if there was a problem installing something
+ """
+ # This resets the transaction
+ dbo.reset(goal=True)
+ forname,versioninprojects:
+ try:
+ ifnotversion:
+ version="*"
+ pkgs=[pkgforpkgindnf.subject.Subject(name).get_best_query(dbo.sack).filter(version__glob=version,latest=True)]
+ ifnotpkgs:
+ raiseProjectsError("No match for %s-%s"%(name,version))
+
+ forpinpkgs:
+ dbo.package_install(p)
+ exceptdnf.exceptions.MarkingError:
+ raiseProjectsError("No match for %s-%s"%(name,version))
+
+
+
[docs]defprojects_depsolve(dbo,projects):"""Return the dependencies for a list of projects :param dbo: dnf base object :type dbo: dnf.Base
- :param project_names: The projects to find the dependencies for
- :type project_names: List of Strings
+ :param projects: The projects to find the dependencies for
+ :type projects: List of Strings :returns: NEVRA's of the project and its dependencies :rtype: list of dicts
+ :raises: ProjectsError if there was a problem installing something """
- # This resets the transaction
- dbo.reset(goal=True)
- forpinproject_names:
- try:
- dbo.install(p)
- exceptdnf.exceptions.MarkingError:
- raiseProjectsError("No match for %s"%p)
+ _depsolve(dbo,projects)try:dbo.resolve()exceptdnf.exceptions.DepsolveErrorase:
- raiseProjectsError("There was a problem depsolving %s: %s"%(project_names,str(e)))
+ raiseProjectsError("There was a problem depsolving %s: %s"%(projects,str(e)))iflen(dbo.transaction)==0:return[]
@@ -387,7 +411,7 @@
returninstalled_size
[docs]defprojects_depsolve_with_size(dbo,projects,with_core=True):"""Return the dependencies and installed size for a list of projects :param dbo: dnf base object
@@ -396,14 +420,9 @@
:type project_names: List of Strings :returns: installed size and a list of NEVRA's of the project and its dependencies :rtype: tuple of (int, list of dicts)
+ :raises: ProjectsError if there was a problem installing something """
- # This resets the transaction
- dbo.reset(goal=True)
- forpinproject_names:
- try:
- dbo.install(p)
- exceptdnf.exceptions.MarkingError:
- raiseProjectsError("No match for %s"%p)
+ _depsolve(dbo,projects)ifwith_core:dbo.group_install("core",['mandatory','default','optional'])
@@ -411,7 +430,7 @@
try:dbo.resolve()exceptdnf.exceptions.DepsolveErrorase:
- raiseProjectsError("There was a problem depsolving %s: %s"%(project_names,str(e)))
+ raiseProjectsError("There was a problem depsolving %s: %s"%(projects,str(e)))iflen(dbo.transaction)==0:return(0,[])
@@ -456,9 +475,218 @@
# Add the dependency info to each oneformoduleinmodules:
- module["dependencies"]=projects_depsolve(dbo,[module["name"]])
+ module["dependencies"]=projects_depsolve(dbo,[(module["name"],"*.*")])returnmodules
+
+
[docs]defdnf_repo_to_file_repo(repo):
+ """Return a string representation of a DNF Repo object suitable for writing to a .repo file
+
+ :param repo: DNF Repository
+ :type repo: dnf.RepoDict
+ :returns: A string
+ :rtype: str
+
+ The DNF Repo.dump() function does not produce a string that can be used as a dnf .repo file,
+ it ouputs baseurl and gpgkey as python lists which DNF cannot read. So do this manually with
+ only the attributes we care about.
+ """
+ repo_str="[%s]\n"%repo.id
+ ifrepo.metalink:
+ repo_str+="metalink = %s\n"%repo.metalink
+ elifrepo.mirrorlist:
+ repo_str+="mirrorlist = %s\n"%repo.mirrorlist
+ elifrepo.baseurl:
+ repo_str+="baseurl = %s\n"%repo.baseurl[0]
+ else:
+ raiseRuntimeError("Repo has no baseurl, metalink, or mirrorlist")
+
+ # proxy is optional
+ ifrepo.proxy:
+ repo_str+="proxy = %s\n"%repo.proxy
+
+ repo_str+="sslverify = %s\n"%repo.sslverify
+ repo_str+="gpgcheck = %s\n"%repo.gpgcheck
+ ifrepo.gpgkey:
+ repo_str+="gpgkey = %s\n"%",".join(repo.gpgkey)
+
+ returnrepo_str
+
+
[docs]defrepo_to_source(repo,system_source):
+ """Return a Weldr Source dict created from the DNF Repository
+
+ :param repo: DNF Repository
+ :type repo: dnf.RepoDict
+ :param system_source: True if this source is an immutable system source
+ :type system_source: bool
+ :returns: A dict with Weldr Source fields filled in
+ :rtype: dict
+
+ Example::
+
+ {
+ "check_gpg": true,
+ "check_ssl": true,
+ "gpgkey_url": [
+ "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-28-x86_64"
+ ],
+ "name": "fedora",
+ "proxy": "http://proxy.brianlane.com:8123",
+ "system": true
+ "type": "yum-metalink",
+ "url": "https://mirrors.fedoraproject.org/metalink?repo=fedora-28&arch=x86_64"
+ }
+
+ """
+ source={"name":repo.id,"system":system_source}
+ ifrepo.baseurl:
+ source["url"]=repo.baseurl[0]
+ source["type"]="yum-baseurl"
+ elifrepo.metalink:
+ source["url"]=repo.metalink
+ source["type"]="yum-metalink"
+ elifrepo.mirrorlist:
+ source["url"]=repo.mirrorlist
+ source["type"]="yum-mirrorlist"
+ else:
+ raiseRuntimeError("Repo has no baseurl, metalink, or mirrorlist")
+
+ # proxy is optional
+ ifrepo.proxy:
+ source["proxy"]=repo.proxy
+
+ ifnotrepo.sslverify:
+ source["check_ssl"]=False
+ else:
+ source["check_ssl"]=True
+
+ ifnotrepo.gpgcheck:
+ source["check_gpg"]=False
+ else:
+ source["check_gpg"]=True
+
+ ifrepo.gpgkey:
+ source["gpgkey_urls"]=repo.gpgkey
+
+ returnsource
+
+
[docs]defsource_to_repo(source,dnf_conf):
+ """Return a dnf Repo object created from a source dict
+
+ :param source: A Weldr source dict
+ :type source: dict
+ :returns: A dnf Repo object
+ :rtype: dnf.Repo
+
+ Example::
+
+ {
+ "check_gpg": True,
+ "check_ssl": True,
+ "gpgkey_urls": [
+ "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-28-x86_64"
+ ],
+ "name": "fedora",
+ "proxy": "http://proxy.brianlane.com:8123",
+ "system": True
+ "type": "yum-metalink",
+ "url": "https://mirrors.fedoraproject.org/metalink?repo=fedora-28&arch=x86_64"
+ }
+
+ """
+ repo=dnf.repo.Repo(source["name"],dnf_conf)
+ # This will allow errors to be raised so we can catch them
+ # without this they are logged, but the repo is silently disabled
+ repo.skip_if_unavailable=False
+
+ ifsource["type"]=="yum-baseurl":
+ repo.baseurl=source["url"]
+ elifsource["type"]=="yum-metalink":
+ repo.metalink=source["url"]
+ elifsource["type"]=="yum-mirrorlist":
+ repo.mirrorlist=source["url"]
+
+ if"proxy"insource:
+ repo.proxy=source["proxy"]
+
+ ifsource["check_ssl"]:
+ repo.sslverify=True
+ else:
+ repo.sslverify=False
+
+ ifsource["check_gpg"]:
+ repo.gpgcheck=True
+ else:
+ repo.gpgcheck=False
+
+ if"gpgkey_urls"insource:
+ repo.gpgkey=",".join(source["gpgkey_urls"])
+
+ repo.enable()
+
+ returnrepo
+
+
[docs]defget_source_ids(source_path):
+ """Return a list of the source ids in a file
+
+ :param source_path: Full path and filename of the source (yum repo) file
+ :type source_path: str
+ :returns: A list of source id strings
+ :rtype: list of str
+ """
+ ifnotos.path.exists(source_path):
+ return[]
+
+ cfg=ConfigParser()
+ cfg.read(source_path)
+ returncfg.sections()
+
+
[docs]defget_repo_sources(source_glob):
+ """Return a list of sources from a directory of yum repositories
+
+ :param source_glob: A glob to use to match the source files, including full path
+ :type source_glob: str
+ :returns: A list of the source ids in all of the matching files
+ :rtype: list of str
+ """
+ sources=[]
+ forfinglob(source_glob):
+ sources.extend(get_source_ids(f))
+ returnsources
+
+
[docs]defdelete_repo_source(source_glob,source_name):
+ """Delete a source from a repo file
+
+ :param source_glob: A glob of the repo sources to search
+ :type source_glob: str
+ :returns: None
+ :raises: ProjectsError if there was a problem
+
+ A repo file may have multiple sources in it, delete only the selected source.
+ If it is the last one in the file, delete the file.
+
+ WARNING: This will delete ANY source, the caller needs to ensure that a system
+ source_name isn't passed to it.
+ """
+ found=False
+ forfinglob(source_glob):
+ try:
+ cfg=ConfigParser()
+ cfg.read(f)
+ ifsource_nameincfg.sections():
+ found=True
+ cfg.remove_section(source_name)
+ # If there are other sections, rewrite the file without the deleted one
+ iflen(cfg.sections())>0:
+ withopen(f,"w")ascfg_file:
+ cfg.write(cfg_file)
+ else:
+ # No sections left, just delete the file
+ os.unlink(f)
+ exceptExceptionase:
+ raiseProjectsError("Problem deleting repo source %s: %s"%(source_name,str(e)))
+ ifnotfound:
+ raiseProjectsError("source %s not found"%source_name)
@@ -247,7 +247,7 @@
# Pick the oldest and move it into ./run/ifnotuuids:# No composes left to process, sleep for a bit
- time.sleep(30)
+ time.sleep(5)else:src=joinpaths(cfg.composer_dir,"queue/new",uuids[0])dst=joinpaths(cfg.composer_dir,"queue/run",uuids[0])
@@ -353,7 +353,7 @@
test_path=joinpaths(results_dir,"TEST")ifos.path.exists(test_path):# Pretend to run the compose
- time.sleep(10)
+ time.sleep(5)try:test_mode=int(open(test_path,"r").read())exceptException:
@@ -805,7 +805,7 @@
diff --git a/_modules/pylorax/api/recipes.html b/_modules/pylorax/api/recipes.html
index 2e0daf0d..89f9a5be 100644
--- a/_modules/pylorax/api/recipes.html
+++ b/_modules/pylorax/api/recipes.html
@@ -8,7 +8,7 @@
- pylorax.api.recipes — Lorax 29.1 documentation
+ pylorax.api.recipes — Lorax 29.6 documentation
@@ -57,7 +57,7 @@
- 29.1
+ 29.6
@@ -230,11 +230,21 @@
"""Return the names of the packages"""return[p["name"]forpinself["packages"]or[]]
+ @property
+ defpackage_nver(self):
+ """Return the names and version globs of the packages"""
+ return[(p["name"],p["version"])forpinself["packages"]or[]]
+
@propertydefmodule_names(self):"""Return the names of the modules"""return[m["name"]forminself["modules"]or[]]
+ @property
+ defmodule_nver(self):
+ """Return the names and version globs of the modules"""
+ return[(m["name"],m["version"])forminself["modules"]or[]]
+
@propertydeffilename(self):"""Return the Recipe's filename
@@ -1080,7 +1090,7 @@
diff --git a/_modules/pylorax/api/server.html b/_modules/pylorax/api/server.html
index 9b11428e..d9448e70 100644
--- a/_modules/pylorax/api/server.html
+++ b/_modules/pylorax/api/server.html
@@ -8,7 +8,7 @@
- pylorax.api.server — Lorax 29.1 documentation
+ pylorax.api.server — Lorax 29.6 documentation
@@ -57,7 +57,7 @@
@@ -658,6 +658,94 @@
] }
+`/api/v0/projects/source/list`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Return the list of repositories used for depsolving and installing packages.
+
+ Example::
+
+ {
+ "sources": [
+ "fedora",
+ "fedora-cisco-openh264",
+ "fedora-updates-testing",
+ "fedora-updates"
+ ]
+ }
+
+`/api/v0/projects/source/info/<source-names>`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Return information about the comma-separated list of source names. Or all of the
+ sources if '*' is passed. Note that general globbing is not supported, only '*'.
+
+ immutable system sources will have the "system" field set to true. User added sources
+ will have it set to false. System sources cannot be changed or deleted.
+
+ Example::
+
+ {
+ "errors": [],
+ "sources": {
+ "fedora": {
+ "check_gpg": true,
+ "check_ssl": true,
+ "gpgkey_urls": [
+ "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-28-x86_64"
+ ],
+ "name": "fedora",
+ "proxy": "http://proxy.brianlane.com:8123",
+ "system": true,
+ "type": "yum-metalink",
+ "url": "https://mirrors.fedoraproject.org/metalink?repo=fedora-28&arch=x86_64"
+ }
+ }
+ }
+
+POST `/api/v0/projects/source/new`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Add (or change) a source for use when depsolving blueprints and composing images.
+
+ The ``proxy`` and ``gpgkey_urls`` entries are optional. All of the others are required. The supported
+ types for the urls are:
+
+ * ``yum-baseurl`` is a URL to a yum repository.
+ * ``yum-mirrorlist`` is a URL for a mirrorlist.
+ * ``yum-metalink`` is a URL for a metalink.
+
+ If ``check_ssl`` is true the https certificates must be valid. If they are self-signed you can either set
+ this to false, or add your Certificate Authority to the host system.
+
+ If ``check_gpg`` is true the GPG key must either be installed on the host system, or ``gpgkey_urls``
+ should point to it.
+
+ You can edit an existing source (other than system sources), by doing a POST
+ of the new version of the source. It will overwrite the previous one.
+
+ Example::
+
+ {
+ "name": "custom-source-1",
+ "url": "https://url/path/to/repository/",
+ "type": "yum-baseurl",
+ "check_ssl": true,
+ "check_gpg": true,
+ "gpgkey_urls": [
+ "https://url/path/to/gpg-key"
+ ]
+ }
+
+DELETE `/api/v0/projects/source/delete/<source-name>`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Delete a user added source. This will fail if a system source is passed to
+ it.
+
+ The response will be a status response with `status` set to true, or an
+ error response with it set to false and an error message included.
+
`/api/v0/modules/list[?offset=0&limit=20]`^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -1035,11 +1123,14 @@
importosfromflaskimportjsonify,request,Response,send_file
+importpytomlastoml
+frompylorax.sysutilsimportjoinpathsfrompylorax.api.composeimportstart_build,compose_typesfrompylorax.api.crossdomainimportcrossdomainfrompylorax.api.projectsimportprojects_list,projects_info,projects_depsolve
-frompylorax.api.projectsimportmodules_list,modules_info,ProjectsError
+frompylorax.api.projectsimportmodules_list,modules_info,ProjectsError,repo_to_source
+frompylorax.api.projectsimportget_repo_sources,delete_repo_source,source_to_repo,dnf_repo_to_file_repofrompylorax.api.queueimportqueue_status,build_status,uuid_delete,uuid_status,uuid_infofrompylorax.api.queueimportuuid_tar,uuid_image,uuid_cancel,uuid_logfrompylorax.api.recipesimportlist_branch_files,read_recipe_commit,recipe_filename,list_commits
@@ -1338,9 +1429,9 @@
# Combine modules and packages and depsolve the list# TODO include the version/glob in the depsolving
- module_names=blueprint.module_names
- package_names=blueprint.package_names
- projects=sorted(set(module_names+package_names),key=lambdan:n.lower())
+ module_nver=blueprint.module_nver
+ package_nver=blueprint.package_nver
+ projects=sorted(set(module_nver+package_nver),key=lambdap:p[0].lower())deps=[]try:withapi.config["DNFLOCK"].lock:
@@ -1390,9 +1481,9 @@
# Combine modules and packages and depsolve the list# TODO include the version/glob in the depsolving
- module_names=[m["name"]forminblueprint["modules"]or[]]
- package_names=[p["name"]forpinblueprint["packages"]or[]]
- projects=sorted(set(module_names+package_names),key=lambdan:n.lower())
+ module_nver=blueprint.module_nver
+ package_nver=blueprint.package_nver
+ projects=sorted(set(module_nver+package_nver),key=lambdap:p[0].lower())deps=[]try:withapi.config["DNFLOCK"].lock:
@@ -1451,13 +1542,139 @@
"""Return detailed information about the listed projects"""try:withapi.config["DNFLOCK"].lock:
- deps=projects_depsolve(api.config["DNFLOCK"].dbo,project_names.split(","))
+ deps=projects_depsolve(api.config["DNFLOCK"].dbo,[(n,"*")forninproject_names.split(",")])exceptProjectsErrorase:log.error("(v0_projects_depsolve) %s",str(e))returnjsonify(status=False,errors=[str(e)]),400returnjsonify(projects=deps)
+ @api.route("/api/v0/projects/source/list")
+ @crossdomain(origin="*")
+ defv0_projects_source_list():
+ """Return the list of source names"""
+ withapi.config["DNFLOCK"].lock:
+ repos=list(api.config["DNFLOCK"].dbo.repos.iter_enabled())
+ sources=sorted([r.idforrinrepos])
+ returnjsonify(sources=sources)
+
+ @api.route("/api/v0/projects/source/info/<source_names>")
+ @crossdomain(origin="*")
+ defv0_projects_source_info(source_names):
+ """Return detailed info about the list of sources"""
+ out_fmt=request.args.get("format","json")
+
+ # Return info on all of the sources
+ ifsource_names=="*":
+ withapi.config["DNFLOCK"].lock:
+ source_names=",".join(r.idforrinapi.config["DNFLOCK"].dbo.repos.iter_enabled())
+
+ sources={}
+ errors=[]
+ system_sources=get_repo_sources("/etc/yum.repos.d/*.repo")
+ forsourceinsource_names.split(","):
+ withapi.config["DNFLOCK"].lock:
+ repo=api.config["DNFLOCK"].dbo.repos.get(source,None)
+ ifnotrepo:
+ errors.append("%s is not a valid source"%source)
+ continue
+ sources[repo.id]=repo_to_source(repo,repo.idinsystem_sources)
+
+ ifout_fmt=="toml"andnoterrors:
+ # With TOML output we just want to dump the raw sources, skipping the errors
+ returntoml.dumps(sources)
+ elifout_fmt=="toml"anderrors:
+ # TOML requested, but there was an error
+ returnjsonify(status=False,errors=errors),400
+ else:
+ returnjsonify(sources=sources,errors=errors)
+
+ @api.route("/api/v0/projects/source/new",methods=["POST"])
+ @crossdomain(origin="*")
+ defv0_projects_source_new():
+ """Add a new package source. Or change an existing one"""
+ ifrequest.headers['Content-Type']=="text/x-toml":
+ source=toml.loads(request.data)
+ else:
+ source=request.get_json(cache=False)
+
+ system_sources=get_repo_sources("/etc/yum.repos.d/*.repo")
+ ifsource["name"]insystem_sources:
+ returnjsonify(status=False,errors=["%s is a system source, it cannot be deleted."%source["name"]]),400
+
+ try:
+ # Remove it from the RepoDict (NOTE that this isn't explicitly supported by the DNF API)
+ withapi.config["DNFLOCK"].lock:
+ dbo=api.config["DNFLOCK"].dbo
+ # If this repo already exists, delete it and replace it with the new one
+ repos=list(r.idforrindbo.repos.iter_enabled())
+ ifsource["name"]inrepos:
+ deldbo.repos[source["name"]]
+
+ repo=source_to_repo(source,dbo.conf)
+ dbo.repos.add(repo)
+
+ log.info("Updating repository metadata after adding %s",source["name"])
+ dbo.fill_sack(load_system_repo=False)
+ dbo.read_comps()
+
+ # Write the new repo to disk, replacing any existing ones
+ repo_dir=api.config["COMPOSER_CFG"].get("composer","repo_dir")
+
+ # Remove any previous sources with this name, ignore it if it isn't found
+ try:
+ delete_repo_source(joinpaths(repo_dir,"*.repo"),source["name"])
+ exceptProjectsError:
+ pass
+
+ # Make sure the source name can't contain a path traversal by taking the basename
+ source_path=joinpaths(repo_dir,os.path.basename("%s.repo"%source["name"]))
+ withopen(source_path,"w")asf:
+ f.write(dnf_repo_to_file_repo(repo))
+ exceptExceptionase:
+ log.error("(v0_projects_source_add) adding %s failed: %s",source["name"],str(e))
+
+ # Cleanup the mess, if loading it failed we don't want to leave it in memory
+ repos=list(r.idforrindbo.repos.iter_enabled())
+ ifsource["name"]inrepos:
+ withapi.config["DNFLOCK"].lock:
+ dbo=api.config["DNFLOCK"].dbo
+ deldbo.repos[source["name"]]
+
+ log.info("Updating repository metadata after adding %s failed",source["name"])
+ dbo.fill_sack(load_system_repo=False)
+ dbo.read_comps()
+
+ returnjsonify(status=False,errors=[str(e)]),400
+
+ returnjsonify(status=True)
+
+ @api.route("/api/v0/projects/source/delete/<source_name>",methods=["DELETE"])
+ @crossdomain(origin="*")
+ defv0_projects_source_delete(source_name):
+ """Delete the named source and return a status response"""
+ system_sources=get_repo_sources("/etc/yum.repos.d/*.repo")
+ ifsource_nameinsystem_sources:
+ returnjsonify(status=False,errors=["%s is a system source, it cannot be deleted."%source_name]),400
+ share_dir=api.config["COMPOSER_CFG"].get("composer","repo_dir")
+ try:
+ # Remove the file entry for the source
+ delete_repo_source(joinpaths(share_dir,"*.repo"),source_name)
+
+ # Remove it from the RepoDict (NOTE that this isn't explicitly supported by the DNF API)
+ withapi.config["DNFLOCK"].lock:
+ ifsource_nameinapi.config["DNFLOCK"].dbo.repos:
+ delapi.config["DNFLOCK"].dbo.repos[source_name]
+ log.info("Updating repository metadata after removing %s",source_name)
+ api.config["DNFLOCK"].dbo.fill_sack(load_system_repo=False)
+ api.config["DNFLOCK"].dbo.read_comps()
+
+ exceptProjectsErrorase:
+ log.error("(v0_projects_source_delete) %s",str(e))
+ returnjsonify(status=False,errors=[str(e)]),400
+
+ returnjsonify(status=True)
+
@api.route("/api/v0/modules/list")@api.route("/api/v0/modules/list/<module_names>")@crossdomain(origin="*")
@@ -1752,7 +1969,7 @@
diff --git a/_modules/pylorax/api/workspace.html b/_modules/pylorax/api/workspace.html
index b2aa0d0a..aa9662ea 100644
--- a/_modules/pylorax/api/workspace.html
+++ b/_modules/pylorax/api/workspace.html
@@ -8,7 +8,7 @@
- pylorax.api.workspace — Lorax 29.1 documentation
+ pylorax.api.workspace — Lorax 29.6 documentation
@@ -57,7 +57,7 @@
@@ -286,7 +286,7 @@
* Parsing and execution are *separate* passes - so you can't use the result of a command in an %if statement (or any other control statements)!
- * Commands that run external programs (systemctl, gconfset) currently use
+ * Commands that run external programs (e.g. systemctl) currently use the *host*'s copy of that program, which may cause problems if there's a big enough difference between the host and the image you're modifying.
@@ -616,22 +616,6 @@
forfinrglob(self._out(fileglob),fatal=True):os.chmod(f,int(mode,8))
- # TODO: do we need a new command for gsettings?
-
[docs]defgconfset(self,path,keytype,value,outfile=None):
- '''
- gconfset PATH KEYTYPE VALUE [OUTFILE]
- Set the given gconf PATH, with type KEYTYPE, to the given value.
- OUTFILE defaults to /etc/gconf/gconf.xml.defaults if not given.
- Example:
- gconfset /apps/metacity/general/num_workspaces int 1
- '''
- ifoutfileisNone:
- outfile=self._out("etc/gconf/gconf.xml.defaults")
- cmd=["gconftool-2","--direct",
- "--config-source=xml:readwrite:%s"%outfile,
- "--set","--type",keytype,path,value]
- runcmd(cmd)
If you are using your own repositories and installing groups (eg. @core) make
+sure you create the repodata with groups like this createrepo-g
+/path/to/groups.xml/path/to/rpms
One drawback to using qemu is that it pulls the packages from the repo each
time you run it. To speed things up you either need a local mirror of the
packages, or you can use a caching proxy. When using a proxy you pass it to
@@ -714,7 +726,15 @@ packages will get cached, so your kickstart url would look like:
You can also add an update repo, but don’t name it updates. Add –proxy to it
-as well.
+as well. You can use all of the kickstart commands in your kickstart. Make sure there
+is only one url command, other repos have to use the repo command and cannot be
+named updates which is reserved for Anaconda’s use. eg.:
+
@@ -1078,6 +1098,16 @@ cancel the installation when they happen. But not everything can be caught.
When creating a new kickstart it is helpful to use vnc so that you can monitor
the installation as it happens, and if it gets stuck without lmc detecting the
problem you can switch to tty1 and examine the system directly.
+
If you suspect problems with %pre or %post sections you can redirect the output
+to the terminal and examine it by logging into the VM. eg.:
@@ -205,11 +206,10 @@ installation and configuration of the images.
The best way to install lorax-composer is to use sudodnfinstalllorax-composercomposer-cli, this will setup the weldr user and install the
systemd socket activation service. You will then need to enable it with sudo
-systemctlenablelorax-composer.socket
-&&sudo
-systemctlstartlorax-composer.socket. This will leave the server off until
-the first request is made. Systemd will then launch the server and it will
-remain running until the system is rebooted.
+systemctlenablelorax-composer.socket&&sudosystemctlstart
+lorax-composer.socket. This will leave the server off until the first request
+is made. Systemd will then launch the server and it will remain running until
+the system is rebooted.
@@ -218,9 +218,8 @@ remain running until the system is rebooted.
Remove any pre-existing socket directory with rm-rf/run/weldr/
A new directory with correct permissions will be created the first time the server runs.
Enable the socket activation with systemctlenablelorax-composer.socket
-&&sudo
-systemctlstartlorax-composer.socket or
-run it directly with lorax-composer/path/to/blueprints/
+&&sudosystemctlstartlorax-composer.socket or run it directly with
+lorax-composer/path/to/blueprints/
The /path/to/blueprints/ directory is where the blueprints’ git repo will
be created, and all the blueprints created with the /api/v0/blueprints/new
@@ -471,6 +470,50 @@ unneeded extra files. This is especially true for the
+
By default lorax-composer uses the host’s configured repositories. It copies
+the *.repo files from /etc/yum.repos.d/ into
+/var/lib/lorax/composer/repos.d/ at startup, these are immutable system
+repositories and cannot be deleted or changed. If you want to add additional
+repos you can put them into /var/lib/lorax/composer/repos.d/ or use the
+/api/v0/projects/source/* API routes to create them.
+
The new source can be added by doing a POST to the /api/v0/projects/source/new
+route using JSON (with Content-Type header set to application/json) or TOML
+(with it set to text/x-toml). The format of the source looks like this (in
+TOML):
The proxy and gpgkey_urls entries are optional. All of the others are required. The supported
+types for the urls are:
+
+
yum-baseurl is a URL to a yum repository.
+
yum-mirrorlist is a URL for a mirrorlist.
+
yum-metalink is a URL for a metalink.
+
+
If check_ssl is true the https certificates must be valid. If they are self-signed you can either set
+this to false, or add your Certificate Authority to the host system.
+
If check_gpg is true the GPG key must either be installed on the host system, or gpgkey_urls
+should point to it.
+
You can edit an existing source (other than system sources), by doing a POST to the new route
+with the new version of the source. It will overwrite the previous one.
+
A list of existing sources is available from /api/v0/projects/source/list, and detailed info
+on a source can be retrieved with the /api/v0/projects/source/info/<source-name> route. By default
+it returns JSON but it can also return TOML if ?format=toml is added to the request.
+
Non-system sources can be deleted by doing a DELETE request to the
+/api/v0/projects/source/delete/<source-name> route.
+
The documentation for the source API routes can be found here
+
The configured sources are used for all blueprint depsolve operations, and for composing images.
+When adding additional sources you must make sure that the packages in the source do not
+conflict with any other package sources, otherwise depsolving will fail.
+
@@ -515,7 +558,7 @@ the contents of the iso as well as the boot.iso itself.