Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add and Refactor and maintainance PR #50

Merged
merged 29 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
165d2f5
state safety commit
henilp105 Nov 12, 2023
b01a275
refactor : repo
henilp105 Nov 12, 2023
3b76448
fix: docker
henilp105 Nov 14, 2023
057795e
refactor : docker
henilp105 Nov 14, 2023
18d8e23
fix: tests and CI to support email verification
henilp105 Nov 15, 2023
1625c2d
fix: tests for login refactor
henilp105 Nov 15, 2023
6deb0a2
fix: reafctor namespace tests
henilp105 Nov 15, 2023
7449e1a
fix: disable email verification for CI
henilp105 Nov 15, 2023
a400ada
fix: packages
henilp105 Nov 17, 2023
bf21d12
fix:test cases
henilp105 Nov 17, 2023
ee885f4
fix: package validation
henilp105 Nov 17, 2023
b1f9c44
fix: package validation
henilp105 Nov 17, 2023
61ccd87
fix: refactor and improve test cases
henilp105 Nov 19, 2023
9070097
fix: refactor and improve test cases
henilp105 Nov 19, 2023
5531ec9
fix: refactor and improve base test cases
henilp105 Nov 19, 2023
a3a28d9
fix: test env and gitignore
henilp105 Nov 19, 2023
82e9db1
add: exceptions and error handling
henilp105 Nov 20, 2023
fdffa99
fix: bugs and cleanup
henilp105 Nov 25, 2023
5bc2208
update readme
henilp105 Nov 25, 2023
e96f8c2
Merge branch 'main' of https://github.com/henilp105/registry
henilp105 Nov 25, 2023
c936063
clean
henilp105 Nov 25, 2023
e790e32
check workflows
henilp105 Nov 25, 2023
bb3c0a0
fix: vercel prod
henilp105 Nov 26, 2023
9ac9325
fix: vercel prod and docker files
henilp105 Nov 26, 2023
f409e24
fix: test directory
henilp105 Nov 26, 2023
5883b44
fix: directory
henilp105 Nov 26, 2023
a48e669
fix: directory
henilp105 Nov 26, 2023
b14b52c
fix: directory and docker compose for nginx
henilp105 Nov 27, 2023
043b3c7
fix: nginx
henilp105 Nov 27, 2023
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
27 changes: 0 additions & 27 deletions .docker/docker-compose.yaml

This file was deleted.

43 changes: 35 additions & 8 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

/.vscode/
/.docker/
/flask/__pycache__/
/flask/.env
/flask/vercel.json
/flask/static/
/flask/.vercel
/frontend/node_modules/
tests/__pycache__/

# backend
/backend/.env
/backend/vercel.json
/backend/static/*.tar.gz
/backend/__pycache__/
/backend/.vercel
/backend/app.log
/.DS_Store
/flask/.DS_Store
/backend/.DS_Store

# dependencies
/frontend/node_modules
/frontend//.pnp
/frontend/.pnp.js

# testing
.pytest_cache/
/frontend/coverage
tests/__pycache__/

# production
/frontend/build

# misc
/frontend/.DS_Store
/frontend/.env.local
/frontend/.env.development.local
/frontend/.env.test.local
/frontend/.env.production.local

/frontend/npm-debug.log*
/frontend/yarn-debug.log*
/frontend/yarn-error.log*
66 changes: 26 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
# Registry for Fortran package manager
# Registry for Fortran Package Manager

Currently for the testing phase :
1. backend APIs are hosted at: http://registry-apis.vercel.app/
2. frontend is hosted at: https://registry-frontend.vercel.app/
3. Documentation for the APIs are available at: https://registry-apis.vercel.app/apidocs/
We are currently in the testing phase of this registry.

1. backend APIs are hosted at: https://fpm-registry.vercel.app/
2. frontend is hosted at: https://registry-phi.vercel.app/
3. Documentation for the APIs are available at: https://fpm-registry.vercel.app/apidocs/

**Please note: the current registry is a playground: its database will be fully deleted once its functionality is established. Please do not use it for production yet! more information will follow then.**

The fpm release [0.8.2](https://fortran-lang.discourse.group/t/fpm-version-0-8-2-released-centralized-registry-playground/5792) introduces fpm support for uploading packages to the fpm-registry server directly from the command-line interface, via

```
fpm publish --token <token-here>
fpm publish --token <upload-token-here>
```

fpm will now interact with a web interface that will help to manage the namespaces & packages.

fpm will now also interact with a web interface that will help to manage the namespaces & packages. detailed information regarding the fpm cli can be found here: [docs](https://fpm.fortran-lang.org/registry/index.html)

## fpm - registry : Python Flask app with Nginx and Mongo database
## Instructions to Deploy with docker containers

backend Project structure:
```
.
├── compose.yaml
├── flask
│   ├── Dockerfile
│   ├── requirements.txt
│   └── server.py
| └── app.py
| └── auth.py
| └── mongo.py
| └── .env
└── nginx
   └── nginx.conf

$ sudo chmod 666 /var/run/docker.sock # for root access
```

## Instructions for Deploy with docker compose
## Environment variable configuration

set the environment variables in .env file in backend directory or in the docker compose file (compose.yaml). MONGO_URI must be set in the environment to the URL value of the MongoDB to use. For example,If deploying to production, MONGO_URI should be set to mongo container address. set the following env variables in the .env file in the backend folder:
- SALT
- MONGO_URI=MONGO_DB_ATLAS_UR
- MONGO_DB_NAME
- SUDO_PASSWORD
- MONGO_USER_NAME
- MONGO_PASSWORD
- HOST
- RESET_EMAIL
- RESET_PASSWORD

#### before building the docker containers, you must configure the environment variables ("RESET_EMAIL" and "RESET_PASSWORD") in the compose.yaml file .

```
$ sudo chmod 666 /var/run/docker.sock (for root access)
$ docker compose up -d
$ REACT_APP_REGISTRY_API_URL="http://127.0.0.1:9090" npm start run
$ docker compose -f "compose.yaml" up -d --build
$ cd frontend && REACT_APP_REGISTRY_API_URL="http://127.0.0.1:80" npm start run
```

After the application starts, navigate to `http://localhost:80` in your web browser or run:
Expand All @@ -50,19 +49,6 @@ $ curl localhost:80
Hello world, Mongo Flask
```

set MONGO_URI=MONGO_DB_ATLAS_URL (in .env file in flask directory)
The MONGO_URI must be set in the environment (or, alternatively, in the .env file in the flask directory) to the URL value of the MongoDB to use. For example,If deploying to production, MONGO_URI should be set to mongo container address.
set the following env variables in the .env file in the flask folder:
- SALT
- MONGO_URI
- MONGO_DB_NAME
- SUDO_PASSWORD
- MONGO_USER_NAME
- MONGO_PASSWORD
- HOST
- RESET_EMAIL
- RESET_PASSWORD

Stop and remove the containers

```
Expand Down
File renamed without changes.
12 changes: 7 additions & 5 deletions flask/auth.py → backend/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
sudo_password = os.getenv("SUDO_PASSWORD")
fortran_email = os.getenv("RESET_EMAIL")
fortran_password = os.getenv("RESET_PASSWORD")
is_ci = os.getenv("IS_CI", "false")
host = os.getenv("HOST")
env_var["host"] = host
env_var["salt"] = salt
env_var["sudo_password"] = sudo_password
smtp = smtplib.SMTP("smtp.gmail.com", 587)
smtp.starttls()
smtp.login(fortran_email, fortran_password)
if is_ci!="true":
smtp = smtplib.SMTP("smtp.gmail.com", 587)
smtp.starttls()
smtp.login(fortran_email, fortran_password)

except KeyError as err:
print("Add SALT to .env file")
Expand Down Expand Up @@ -60,7 +62,7 @@ def login():
if not user:
return jsonify({"message": "Invalid email or password", "code": 401}), 401

if not user["isverified"]:
if not user["isverified"] and is_ci!='true': # TODO: Uncomment this line to enable email verification
return jsonify({"message": "Please verify your email", "code": 401}), 401

uuid = generate_uuid() if user["loggedCount"] == 0 else user["uuid"]
Expand Down Expand Up @@ -143,7 +145,7 @@ def signup():
else:
user["roles"] = ["user"]
db.users.insert_one(user)
send_verify_email(email)
send_verify_email(email) if is_ci != 'true' else None
return (
jsonify(
{
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ USER root
WORKDIR /home/registry

# Set up fpm
RUN wget https://github.com/fortran-lang/fpm/releases/download/v0.8.0/fpm-0.8.0-linux-x86_64 -4 -O fpm && \
RUN wget https://github.com/fortran-lang/fpm/releases/download/v0.9.0/fpm-0.9.0-linux-x86_64 -4 -O fpm && \
chmod u+x fpm

WORKDIR /home/registry
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
114 changes: 30 additions & 84 deletions flask/packages.py → backend/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@
from datetime import datetime, timedelta
from auth import generate_uuid
from app import swagger
import zipfile
import tarfile
import io
import os
import toml
import shutil
from flasgger.utils import swag_from
from urllib.parse import unquote
import math
import semantic_version
from license_expression import get_spdx_licensing
from io import BytesIO
# import validate_package
# from validate_package import validate


parameters = {
Expand Down Expand Up @@ -252,39 +251,21 @@ def upload():
{"name": package_name, "namespace": namespace_doc["_id"]}
)

if tarball.content_type not in ["application/gzip", "application/zip","application/octet-stream"]:
if tarball.content_type not in ["application/gzip", "application/zip","application/octet-stream","application/x-tar"]:
return jsonify({"code": 400, "message": "Invalid file type"}), 400

tarball_name = "{}-{}.tar.gz".format(package_name, package_version)
# Upload the tarball to the Grid FS storage.

# try: TODO: Enable this after Validation is Enabled
# zipball, tarball = get_file_object(tarball)
# except Exception as e:
# return jsonify({"code": 400, "message": "Invalid package tarball."}), 400

# zipfile_object_id = file_storage.put(
# zipball, content_type=zipball.content_type, filename=tarball_name + ".zip"
# )
# tarfile_object_id = file_storage.put(
# tarball, content_type=tarball.content_type, filename=tarball_name + ".tar.gz"
# )
file_object_id = file_storage.put(
tarball, content_type=tarball.content_type, filename=tarball_name
)

# Extract the package metadata from the tarball's fpm.toml file.
try:
package_data = extract_fpm_toml(tarball)
except Exception as e:
return jsonify({"code": 400, "message": "Invalid package tarball."}), 400

# TODO: Uncomment this when the package validation is enabled
package_data = extract_toml(tarball)
# validate the package with fpm
# valid_package, package_data = validate(tarball,"{}-{}".format(package_name, package_version)) # TODO: Enable this after Validation is Enabled


# if not valid_package: # TODO: Enable this after Validation is Enabled
# return jsonify({"code": 400, "message": "Invalid package"}), 400

# valid_package = validate_package.validate_package(tarball,"{}-{}".format(package_name, package_version))
# if not valid_package:
# return jsonify({"status": "error", "message": "Invalid package", "code": 400}), 400
file_object_id = file_storage.put(tarball, content_type=tarball.content_type, filename=tarball_name)

# No previous recorded versions of the package found.
if not package_doc:
Expand All @@ -295,12 +276,12 @@ def upload():
"description": package_data["description"],
"homepage": package_data["homepage"],
"repository": package_data["repository"],
"copyright": package_data["copyright"],
"license": package_license,
"createdAt": datetime.utcnow(),
"updatedAt": datetime.utcnow(),
"author": user["_id"],
"maintainers": [user["_id"]],
"copyright": package_data["copyright"],
"tags": ["fortran", "fpm"],
"isDeprecated": False,
}
Expand All @@ -321,8 +302,7 @@ def upload():
"dependencies": "Test dependencies",
"createdAt": datetime.utcnow(),
"isDeprecated": False,
# "download_url_zip": f"/tarballs/{zipfile_object_id}", TODO: Uncomment this when the package validation is enabled
# "download_url_tar": f"/tarballs/{tarfile_object_id}",
"download_url": f"/tarballs/{file_object_id}"
}

package_obj["versions"] = []
Expand All @@ -331,7 +311,7 @@ def upload():
package_obj["versions"].append(version_obj)

if dry_run:
return jsonify({"message": "Dry run Successful.", "code": 200})
return jsonify({"message": "Dry run Successful.", "code": 200}), 200

db.packages.insert_one(package_obj)

Expand Down Expand Up @@ -805,53 +785,19 @@ def checkUserUnauthorizedForNamespaceTokenCreation(user_id, namespace_doc):
return str_user_id not in admins_id_list and str_user_id not in maintainers_id_list


def extract_fpm_toml(file_obj):
binary_stream = BytesIO()
binary_stream.write(file_obj.getbuffer())
binary_stream.seek(0)
tar = tarfile.open(fileobj=binary_stream, mode='r')
fpm_toml_file = None
for file in tar.getmembers():
if file.name == 'fpm.toml':
fpm_toml_file = file
break

if fpm_toml_file is None:
raise ValueError("fpm.toml file not found in the tarball.")

extracted_file = tar.extractfile(fpm_toml_file)
toml_data = extracted_file.read()
tar.close()
parsed_toml = toml.loads(str(toml_data, 'utf-8'))
return parsed_toml

def convert_zip_to_tar(zip_file):
tar_file = io.BytesIO()
with zipfile.ZipFile(zip_file, 'r') as zip_ref:
with tarfile.open(fileobj=tar_file, mode='w') as tar_ref:
for file_name in zip_ref.namelist():
file_data = zip_ref.read(file_name)
tar_info = tarfile.TarInfo(name=file_name)
tar_info.size = len(file_data)
tar_ref.addfile(tar_info, fileobj=io.BytesIO(file_data))

tar_file.seek(0) # Reset the file position for reading
return tar_file


def convert_to_zip(file_obj):
with tarfile.open(file_obj, "r:gz") as tar:
tar.extractall()
with zipfile.ZipFile("package.zip", "w") as zip_ref:
zip_ref.write("fpm.toml")
zip_ref.write("package")
return zip_ref


def get_file_object(file_obj):
if file_obj.content_type == "application/zip":
return file_obj, convert_zip_to_tar(file_obj)
elif file_obj.content_type == "application/gzip":
return convert_to_zip(file_obj), file_obj
else:
raise Exception("Invalid file type")
def extract_toml(file):
with open('static/temp/temp.tar.gz', 'wb') as f:
f.write(file.read())
with tarfile.open('static/temp/temp.tar.gz', "r") as tar:
tar.extractall("static/temp")

for root, dirs, files in os.walk('static/temp'):
file_name = 'fpm.toml'
if file_name in files:
file_path = os.path.join(root, file_name)
with open(file_path, 'r') as file:
file_content = file.read()
shutil.rmtree('static/temp')
os.makedirs('static/temp', exist_ok=True)
parsed_toml = toml.loads(file_content)
return parsed_toml
File renamed without changes.
Loading