Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fusesoc/capi2/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __init__(
if cd_provider:
self.files_root = os.path.join(cache_root, self.name.sanitized_name)
self.provider = get_provider(cd_provider["name"])(
cd_provider, self.core_root, self.files_root
cd_provider, self.core_root, self.files_root, cd_provider["name"]
)
else:
self.files_root = self.core_root
Expand Down
18 changes: 18 additions & 0 deletions fusesoc/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def __init__(self, path=None):
logger.debug("cache_root=" + self.cache_root)
logger.debug("library_root=" + self.library_root)
logger.debug("ssh-trustfile=" + (self.ssh_trustfile or "none"))
logger.debug("publish-uri=" + (self.publish_uri or "none"))
logger.debug("publish-uri-pem=" + (self.publish_uri_pem or "none"))

def _parse_library(self):
# Parse library sections
Expand Down Expand Up @@ -199,6 +201,22 @@ def ssh_trustfile(self):
def ssh_trustfile(self, val):
self._set_default_section("ssh-trustfile", val)

@property
def publish_uri(self):
return self._cp.get(Config.default_section, "publish-uri", fallback=None)

@publish_uri.setter
def publish_uri(self, val):
self._set_default_section("publish-uri", val)

@property
def publish_uri_pem(self):
return self._cp.get(Config.default_section, "publish-uri-pem", fallback=None)

@publish_uri_pem.setter
def publish_uri_pem(self, val):
self._set_default_section("publish-uri-pem", val)

@property
def library_root(self):
return self._get_library_root()
Expand Down
140 changes: 140 additions & 0 deletions fusesoc/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
# SPDX-License-Identifier: BSD-2-Clause

import argparse
import json
import os
import pathlib
import shutil
import signal
import subprocess
import sys
import warnings
from pathlib import Path

import argcomplete
import requests

from fusesoc import signature

Expand Down Expand Up @@ -298,6 +301,127 @@ def core_sign(fs, args):
print(f"{sigfile} created")


def guess_provider():
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is quite a lot of new code, I would prefer to have this in a separate publish.py. main.py is already way too large for its own good.

guess = {"found": False}
cmd = ["git", "remote", "-v"]
res = subprocess.run(cmd, capture_output=True).stdout.decode("utf-8").strip()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's run this from the root directory of the core instead. We can pick up the directory from core.core_root in core_publish and send it to guess_provider as an argument. The reason is that most FuseSoC users have a workspace which is separate from the directories where the cores are.

lines = res.splitlines()
if len(lines) < 1:
return guess
fetchlines = list(filter(lambda s: s.endswith("(fetch)"), lines))
if len(fetchlines) < 1:
return guess
comps = fetchlines[0].split("/")
if comps[2] == "github.com":
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails when there are more than one remote, e g.

$ git remote -v
fork	[email protected]:olofk/common_cells.git (fetch)
fork	[email protected]:olofk/common_cells.git (push)
origin	https://github.com/pulp-platform/common_cells (fetch)
origin	https://github.com/pulp-platform/common_cells (push)

In this particular case we might detect this situation and query the user, but my gut feeling tells me that there might be several more cases like this, so I think it would be good to have CLI options to manually specify at least the upstream URI

guess["name"] = "github"
else:
guess["name"] = comps[2]
user = comps[3]
repo = comps[4]
repo = repo[: len(repo) - len("(fetch)")].strip()
cmd = ["git", "log", "-n", "1"]
res = (
subprocess.run(cmd, capture_output=True)
.stdout.decode("utf-8")
.strip()
.splitlines()[0]
)
comps = res.split(" ")
if (len(comps) >= 2) and (comps[0] == "commit"):
version = comps[1]
else:
version = ""
guess[
"yaml"
] = """provider:
name : {}
user : {}
repo : {}
version : {}
""".format(
guess["name"], user, repo, version
)
guess["found"] = True
return guess


def core_publish(fs, args):
core = _get_core(fs, args.core)
uri = fs.config.publish_uri
pem = fs.config.publish_uri_pem
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean specifically the pem option? It was needed during development since that server was self signed, and why not keep the option?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing HTTPS connections with self-signed certificates - or even HTTP connections - could be a useful feature for local deployments or testing of the FuseSoC Package Directory Server.

sigfile = core.core_file + ".sig"
if (core.provider != None) and (core.provider.name != "github"):
print(
'The provider for this core is "'
+ core.provider.name
+ '" and only "github" is supported for publishing. Aborting.'
)
return False
if core.provider == None:
provider_info = guess_provider()
if provider_info["found"] == False:
print(
"No provider is given in core file or guessable from current project, and a provider of type github is needed for publishing. Aborting."
)
return False
if provider_info["name"] != "github":
print(
"No provider is given in core file, and the current project appears to not be on github, which is needed for publishing. Aborting."
)
return False
print(
"No provider is given in core file, but the current project seems to be on github."
)
if not args.autoprovider:
print(
"The following provider section can be added to the core file if the --autoprovider flag is given to this command."
)
print(provider_info["yaml"])
return False
print("Adding the following provider section to the core file.")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to avoid adding the provider section to the .core file in the repo. Let's copy the core file to a temporary file instead where we can add the section and publish

print(provider_info["yaml"])
cf = open(core.core_file, "ab")
cf.write(("\n" + provider_info["yaml"] + "\n").encode("utf-8"))
cf.close()
print("Now retry publishing.")
return False

print("Core provider: " + core.provider.name)
print("Publish core file: " + core.core_file)
fob_core = open(core.core_file, "rb")
body = {"core_file": fob_core}
fob_sig = None
if os.path.exists(sigfile):
print("and signature file: " + sigfile)
fob_sig = open(sigfile, "rb")
body["signature_file"] = fob_sig
else:
print("(without signature file)")
sf_data = None
if pem:
print("with certificate from: " + pem)
print("to api at: " + uri)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails if uri is not defined

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want a hardcoded default uri? If so, what should it point to?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you like to add a default, pointing to the "official" FOSSI Foundation server instance might be a good option:
https://fusesoc.fossi-foundation.net/

if args.yes:
print("without confirmation")
else:
c = input("Confirm by typing 'yes': ")
if c != "yes":
print("Aborted.")
return False

target = uri + "/v1/publish/"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The APIs entry point is at /api, so the full path /api/v1/publish/ (see Publish a core file).
Seems like the /api part is missing in the target generated URL.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I choose to put the api part in the publish-uri setting (e.g. publish-uri = https://fscpd.qamcom.se/api). I thought it made more sense to let that that point to the root of the api, and then let the code build in that.

logger.debug("POST to " + target)
res = requests.post(target, files=body, allow_redirects=True, verify=pem)
if not res.ok:
print("Request returned http result", res.status_code, res.reason)
err = json.loads(res.content)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to catch more errors here. If the server responds 404 it looks like there isn't a valid json reply at all

Request returned http result 404 Not Found
Traceback (most recent call last):
  File "/home/olof/projects/serv/bin/fusesoc", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/olof/projects/fusesoc/fusesoc/fusesoc/main.py", line 950, in main
    fusesoc(args)
  File "/home/olof/projects/fusesoc/fusesoc/fusesoc/main.py", line 940, in fusesoc
    args.func(fs, args)
  File "/home/olof/projects/fusesoc/fusesoc/fusesoc/main.py", line 417, in core_publish
    err = json.loads(res.content)
          ^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.12/json/decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 2 column 1 (char 1)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a good server to test this against today without causing havoc? I have not been keeping up with recent deployments and deprecations.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our internal test server is still up an running.
Alternative you can spin up your own test server following FuseSoC Package Directory Quick Start or Development Guide.

print(json.dumps(err, indent=4))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to handle known errors, e.g. validation errors, in a more user-friendly way. Instead of

Request returned http result 400 Bad Request
{
    "non_field_errors": [
        "Validation error in core::: 'description' is a required property"
    ]
}

we could look for non_field_errors and write something like Validation error: 'description' is a required property

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be just the forwarded error messages from the FuseSoC-PD Server. So I guess it is more an server side issue.
On server side, there are already some mechanism in place to replace the default error messages from core parsing/validation with custom messages (but there aren't much implemented right now, see end of core_directory/serializers.py)

res.close()
fob_core.close()
if fob_sig:
fob_sig.close()


def gen_clean(fs, args):
cachedir = os.path.join(fs.config.cache_root, "generator_cache")
shutil.rmtree(cachedir, ignore_errors=True)
Expand Down Expand Up @@ -538,6 +662,22 @@ def get_parser():
parser_core_sign.add_argument("keyfile", help="File containing ssh private key")
parser_core_sign.set_defaults(func=core_sign)

parser_core_publish = core_subparsers.add_parser(
"publish", help="Publish core to package db"
)
parser_core_publish.add_argument(
"core", help="Name of the core to publish"
).completer = CoreCompleter()
parser_core_publish.add_argument(
"--yes", help="Skip confirmation", action="store_true"
)
parser_core_publish.add_argument(
"--autoprovider",
help="Automatically add provider section if missing and possible to guess",
action="store_true",
)
parser_core_publish.set_defaults(func=core_publish)

# tool subparser
parser_tool = subparsers.add_parser(
"tool", help="Subcommands for dealing with tools"
Expand Down
3 changes: 2 additions & 1 deletion fusesoc/provider/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ def get_provider(name):


class Provider:
def __init__(self, config, core_root, files_root):
def __init__(self, config, core_root, files_root, name):
self.config = config
self.core_root = core_root
self.files_root = files_root
self.cachable = not (config.get("cachable", "") == False)
self.patches = config.get("patches", [])
self.name = name

def clean_cache(self):
def _make_tree_writable(topdir):
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies = [
"simplesat>=0.9.1",
"fastjsonschema",
"argcomplete",
"requests",
]
requires-python = ">=3.6, <4"

Expand Down
Loading