Skip to content

Commit 1a99478

Browse files
committed
Initial commit
0 parents  commit 1a99478

21 files changed

+1060
-0
lines changed

.ci/aptPackagesToInstall.txt

Whitespace-only changes.

.ci/pythonPackagesToInstallFromGit.txt

Whitespace-only changes.

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_style = tab
6+
indent_size = 4
7+
insert_final_newline = true
8+
end_of_line = lf
9+
10+
[*.{yml,yaml}]
11+
indent_style = space
12+
indent_size = 2

.github/.templateMarker

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
KOLANICH/python_project_boilerplate.py

.github/dependabot.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "pip"
4+
directory: "/"
5+
schedule:
6+
interval: "daily"
7+
allow:
8+
- dependency-type: "all"

.github/workflows/CI.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
name: CI
2+
on:
3+
push:
4+
branches: [master]
5+
pull_request:
6+
branches: [master]
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-22.04
11+
steps:
12+
- name: typical python workflow
13+
uses: KOLANICH-GHActions/typical-python-workflow@master
14+
with:
15+
github_token: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
__pycache__
2+
*.py[co]
3+
/*.egg-info
4+
*.srctrlbm
5+
*.srctrldb
6+
build
7+
dist
8+
.eggs
9+
monkeytype.sqlite3
10+
/.ipynb_checkpoints

.gitlab-ci.yml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
image: registry.gitlab.com/kolanich-subgroups/docker-images/fixed_python:latest
2+
3+
variables:
4+
DOCKER_DRIVER: overlay2
5+
SAST_ANALYZER_IMAGE_TAG: latest
6+
SAST_DISABLE_DIND: "true"
7+
SAST_CONFIDENCE_LEVEL: 5
8+
CODECLIMATE_VERSION: latest
9+
10+
include:
11+
- template: SAST.gitlab-ci.yml
12+
- template: Code-Quality.gitlab-ci.yml
13+
- template: License-Management.gitlab-ci.yml
14+
15+
build:
16+
tags:
17+
- shared
18+
- linux
19+
stage: build
20+
variables:
21+
GIT_DEPTH: "1"
22+
PYTHONUSERBASE: ${CI_PROJECT_DIR}/python_user_packages
23+
24+
before_script:
25+
- export PATH="$PATH:$PYTHONUSERBASE/bin" # don't move into `variables`
26+
- apt-get update
27+
# todo:
28+
#- apt-get -y install
29+
#- pip3 install --upgrade
30+
#- python3 ./fix_python_modules_paths.py
31+
32+
script:
33+
- python3 -m build -nw bdist_wheel
34+
- mv ./dist/*.whl ./dist/tuft-0.CI-py3-none-any.whl
35+
- pip3 install --upgrade ./dist/*.whl
36+
- coverage run --source=tuft -m --branch pytest --junitxml=./rspec.xml ./tests/test.py
37+
- coverage report -m
38+
- coverage xml
39+
40+
coverage: "/^TOTAL(?:\\s+\\d+){4}\\s+(\\d+%).+/"
41+
42+
cache:
43+
paths:
44+
- $PYTHONUSERBASE
45+
46+
artifacts:
47+
paths:
48+
- dist
49+
reports:
50+
junit: ./rspec.xml
51+
cobertura: ./coverage.xml

Code_Of_Conduct.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
No codes of conduct!

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
include UNLICENSE
2+
include *.md
3+
include tests
4+
include .editorconfig

ReadMe.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
tuft.py [![Unlicensed work](https://raw.githubusercontent.com/unlicense/unlicense.org/master/static/favicon.png)](https://unlicense.org/)
2+
=======
3+
~~[wheel (GitLab)](https://gitlab.com/KOLANICH-libs/tuft.py/-/jobs/artifacts/master/raw/dist/tuft-0.CI-py3-none-any.whl?job=build)~~
4+
[wheel (GHA via `nightly.link`)](https://nightly.link/KOLANICH-libs/tuft.py/workflows/CI/master/tuft-0.CI-py3-none-any.whl)
5+
~~![GitLab Build Status](https://gitlab.com/KOLANICH-libs/tuft.py/badges/master/pipeline.svg)~~
6+
~~![GitLab Coverage](https://gitlab.com/KOLANICH-libs/tuft.py/badges/master/coverage.svg)~~
7+
[![GitHub Actions](https://github.com/KOLANICH-libs/tuft.py/workflows/CI/badge.svg)](https://github.com/KOLANICH-libs/tuft.py/actions/)
8+
[![Libraries.io Status](https://img.shields.io/librariesio/github/KOLANICH-libs/tuft.py.svg)](https://libraries.io/github/KOLANICH-libs/tuft.py)
9+
10+
This is a small high-level library for creating [The Update Framework](https://github.com/theupdateframework/specification) repositories.
11+
12+
This library is **insecure**:
13+
14+
* it has never been audited and/or even throughly tested.
15+
* repo validation when it is created is not implemented. If a repo invalid or tampered, it will still be signed by your signature.

UNLICENSE

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
This is free and unencumbered software released into the public domain.
2+
3+
Anyone is free to copy, modify, publish, use, compile, sell, or
4+
distribute this software, either in source code form or as a compiled
5+
binary, for any purpose, commercial or non-commercial, and by any
6+
means.
7+
8+
In jurisdictions that recognize copyright laws, the author or authors
9+
of this software dedicate any and all copyright interest in the
10+
software to the public domain. We make this dedication for the benefit
11+
of the public at large and to the detriment of our heirs and
12+
successors. We intend this dedication to be an overt act of
13+
relinquishment in perpetuity of all present and future rights to this
14+
software under copyright law.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
OTHER DEALINGS IN THE SOFTWARE.
23+
24+
For more information, please refer to <https://unlicense.org/>

pyproject.toml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[build-system]
2+
requires = ["setuptools>=61.2.0", "setuptools_scm[toml]>=3.4.3"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "tuft"
7+
readme = "ReadMe.md"
8+
description = "A small high-level library for creating The Update Framework repositories."
9+
authors = [{name = "KOLANICH"}]
10+
classifiers = [
11+
"Development Status :: 4 - Beta",
12+
"Environment :: Other Environment",
13+
"Intended Audience :: Developers",
14+
"License :: Public Domain",
15+
"Operating System :: OS Independent",
16+
"Programming Language :: Python",
17+
"Programming Language :: Python :: 3",
18+
"Programming Language :: Python :: 3 :: Only",
19+
"Topic :: Software Development :: Libraries :: Python Modules",
20+
]
21+
keywords = ["tuf", "update", "framework"]
22+
license = {text = "Unlicense"}
23+
requires-python = ">=3.4"
24+
dynamic = ["version"]
25+
dependencies = [
26+
"requests", # @ git+https://github.com/psf/requests.git
27+
"requests-file", # @ git+https://github.com/dashea/requests-file.git
28+
"securesystemslib", # @ git+https://github.com/secure-systems-lab/securesystemslib.git
29+
"tuf", # @ git+https://github.com/theupdateframework/python-tuf.git
30+
"transformerz" # @ git+https://github.com/KOLANICH-libs/transformerz.py.git
31+
]
32+
33+
[project.urls]
34+
Homepage = "https://github.com/KOLANICH-libs/tuft.py"
35+
36+
[tool.setuptools]
37+
zip-safe = true
38+
39+
[tool.setuptools.packages.find]
40+
include = ["tuft", "tuft.*"]
41+
42+
[tool.setuptools_scm]

tests/testKey

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-----BEGIN OPENSSH PRIVATE KEY-----
2+
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
3+
QyNTUxOQAAACBhnM5j7/5MfF1wJJKG7UZFX0Eg7T2VNGBTQE+blBMqwgAAAJByJ06ecidO
4+
ngAAAAtzc2gtZWQyNTUxOQAAACBhnM5j7/5MfF1wJJKG7UZFX0Eg7T2VNGBTQE+blBMqwg
5+
AAAEA5JIS+oXe1en5uN4aHO9IY2SFKsadvohK2h62u0nZBEGGczmPv/kx8XXAkkobtRkVf
6+
QSDtPZU0YFNAT5uUEyrCAAAACHRlc3Qga2V5AQIDBAU=
7+
-----END OPENSSH PRIVATE KEY-----

tests/tests.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env python3
2+
import sys
3+
from pathlib import Path
4+
import unittest
5+
import itertools, re
6+
7+
sys.path.insert(0, str(Path(__file__).parent.parent))
8+
9+
from collections import OrderedDict
10+
11+
dict = OrderedDict
12+
13+
import tuft
14+
from tuft import *
15+
16+
17+
class Tests(unittest.TestCase):
18+
19+
def testSimple(self):
20+
raise NotImplementedError
21+
22+
23+
if __name__ == "__main__":
24+
unittest.main()

tuft/Repo.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import typing
2+
from pathlib import Path, PurePath
3+
4+
import requests
5+
from requests_file import FileAdapter
6+
from tuf.ngclient import Updater
7+
from tuf.ngclient._internal.requests_fetcher import RequestsFetcher
8+
9+
from .serializer import jsonFancySerializer
10+
11+
12+
class RequestsFetcherWithFile(RequestsFetcher):
13+
def __init__(self) -> None:
14+
super().__init__()
15+
self.fileAdapter = FileAdapter()
16+
17+
def _get_session(self, url: str) -> requests.Session:
18+
s = super()._get_session(url)
19+
s.mount("file://", self.fileAdapter)
20+
return s
21+
22+
23+
class Repo:
24+
__slots__ = ("localPath", "localMetadata", "updater")
25+
26+
SETTINGS_SERIALIZER = jsonFancySerializer
27+
META_SERIALIZER = jsonFancySerializer
28+
29+
@classmethod
30+
def setup(cls, reposRoot: Path, rootDict: dict, petname: str, baseURIs: typing.Optional[typing.Iterable[str]] = None) -> typing.Dict[str, typing.List[str]]:
31+
localPath = reposRoot / petname
32+
metaDir = cls.metaDirFromLocalPath(localPath)
33+
rootFilePath = cls.rootFilePathFromMetaDir(metaDir)
34+
rootFilePath.write_text(cls.META_SERIALIZER.unprocess(rootDict))
35+
36+
localMetaFilePath = cls.localMetaFilePathFromReposRoot(localPath)
37+
localMetaDict = {"baseURIs": []} # type: typing.Dict[str, typing.Any]
38+
if baseURIs:
39+
localMetaDict["baseURIs"].extend(baseURIs)
40+
localMetaFilePath.write_text(cls.SETTINGS_SERIALIZER.unprocess(localMetaDict))
41+
return localMetaDict
42+
43+
LOCAL_META_FILE_NAME = "local"
44+
45+
@classmethod
46+
def localMetaFilePathFromReposRoot(cls, localPath: Path) -> Path:
47+
return localPath / (cls.LOCAL_META_FILE_NAME + "." + cls.SETTINGS_SERIALIZER.fileExtension)
48+
49+
ROOT_FILE_NAME = "root"
50+
51+
@classmethod
52+
def rootFilePathFromMetaDir(cls, metaDir: Path) -> Path:
53+
return metaDir / (cls.ROOT_FILE_NAME + "." + cls.META_SERIALIZER.fileExtension)
54+
55+
@classmethod
56+
def metaDirFromLocalPath(cls, localPath: Path) -> Path:
57+
res = localPath / "meta"
58+
res.mkdir(exist_ok=True, parents=True)
59+
return res
60+
61+
@classmethod
62+
def repoDirFromLocalPath(cls, localPath: Path) -> Path:
63+
res = localPath / "repo"
64+
res.mkdir(exist_ok=True, parents=True)
65+
return res
66+
67+
def __init__(self, localPath: Path, localMetadata: typing.Optional[dict] = None) -> None:
68+
if localMetadata is None:
69+
localMetadata = {}
70+
71+
self.localPath = localPath
72+
self.localMetadata = localMetadata
73+
self.refreshLocalMetadata()
74+
self.updater = typing.cast(Updater, None)
75+
self.initTUF()
76+
77+
@property
78+
def baseURI(self) -> str:
79+
return self.baseURIs[0]
80+
81+
@property
82+
def baseURIs(self) -> typing.List[str]:
83+
return self.localMetadata["baseURIs"]
84+
85+
def initTUF(self) -> None:
86+
self.updater = Updater(metadata_dir=str(self.__class__.metaDirFromLocalPath(self.localPath)), metadata_base_url=self.baseURI, target_base_url=self.baseURI, target_dir=str(self.__class__.repoDirFromLocalPath(self.localPath)), fetcher=RequestsFetcherWithFile())
87+
88+
def refreshLocalMetadata(self) -> None:
89+
localMetaFile = self.__class__.localMetaFilePathFromReposRoot(self.localPath)
90+
self.localMetadata = self.__class__.SETTINGS_SERIALIZER.process(localMetaFile.read_text())
91+
92+
def update(self) -> None:
93+
self.updater.refresh()
94+
95+
def getListPath(self, path: PurePath) -> Path:
96+
info = self.updater.get_targetinfo(str(path))
97+
98+
if info:
99+
cached = self.updater.find_cached_target(info)
100+
if not cached:
101+
self.updater.download_target(info)
102+
cached = self.updater.find_cached_target(info)
103+
104+
if not cached:
105+
raise KeyError(path)
106+
107+
return Path(cached)
108+
109+
raise KeyError(path)
110+
111+
def __iter__(self) -> typing.Iterable[PurePath]:
112+
for fns in self.updater._trusted_set.targets.signed.targets:
113+
fn = PurePath(fns)
114+
yield fn
115+
116+
def __getitem__(self, k: PurePath) -> Path:
117+
return self.getListPath(k)

0 commit comments

Comments
 (0)