Skip to content
This repository was archived by the owner on Jan 30, 2024. It is now read-only.

Commit b22ece4

Browse files
committed
First commit
1 parent 2d63f44 commit b22ece4

9 files changed

Lines changed: 314 additions & 2 deletions

File tree

.devcontainer/Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# OS
2+
ARG VARIANT=bullseye
3+
FROM --platform=linux/amd64 mcr.microsoft.com/vscode/devcontainers/base:0-${VARIANT}
4+
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive && apt-get install -y firefox-esr
5+
RUN sudo apt-get update
6+
RUN sudo apt-get install -y libgtk-3-dev
7+
RUN sudo apt-get install -y python3 python3-pip
8+
WORKDIR /workspaces/SDS-in-a-box/

.devcontainer/devcontainer.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "Hello",
3+
"build": {
4+
"dockerfile": "Dockerfile",
5+
"args": {
6+
"VARIANT": "bullseye"
7+
}
8+
},
9+
"features": {
10+
"ghcr.io/devcontainers/features/desktop-lite:1": {
11+
"version": "latest"
12+
}, "ghcr.io/devcontainers/features/docker-in-docker:2": {
13+
"version": "latest"
14+
}
15+
},
16+
"forwardPorts": [6080],
17+
"postCreateCommand": "pip install ."
18+
}

.github/workflows/test.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
cdk-tests:
11+
runs-on: ubuntu-latest
12+
defaults:
13+
run:
14+
shell: bash -l {0}
15+
16+
steps:
17+
- uses: actions/checkout@v2
18+
- uses: actions/setup-python@v2
19+
with:
20+
python-version: "3.9"
21+
22+
- name: Install dependencies
23+
run: |
24+
python -m pip install .[dev]
25+
- name: Install the app
26+
run: |
27+
python -m pip install --no-deps -e .
28+
- name: Testing
29+
id: test
30+
run: |
31+
# Ignore the network marks from the remote test environment
32+
python -m pytest --color=yes -m "not network"

.gitignore

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
*.swp
2+
package-lock.json
3+
__pycache__
4+
.pytest_cache
5+
.venv
6+
*.egg-info
7+
.aws
8+
external
9+
.idea/
10+
11+
# Distribution / packaging
12+
.Python
13+
build/
14+
develop-eggs/
15+
dist/
16+
downloads/
17+
eggs/
18+
.eggs/
19+
lib/
20+
lib64/
21+
parts/
22+
sdist/
23+
var/
24+
wheels/
25+
share/python-wheels/
26+
*.egg-info/
27+
.installed.cfg
28+
*.egg
29+
MANIFEST
30+
31+
# CDK asset staging directory
32+
.cdk.staging
33+
cdk.out
34+
aws
35+
awscliv2.zip

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023 IMAP Science Operations Center
3+
Copyright (c) 2023 The Regents of the University of Colorado.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
# sds-scripts
1+
# SDS-access-lib
2+
3+
This is a simple python script that allows a user to upload, query, and download data from a a Science Data System as set up from: https://github.com/IMAP-Science-Operations-Center/sds-data-manager

pyproject.toml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
[build-system]
2+
requires = ["setuptools", "setuptools-scm"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[project]
6+
name = "SDS-in-a-box"
7+
version = "0.1.0"
8+
description = "IMAP Science Operations Center SDS-in-a-box"
9+
readme = "README.md"
10+
license = {text = "MIT"}
11+
keywords = ["IMAP", "SDC", "SOC", "Science Operations"]
12+
classifiers = [
13+
"Development Status :: 3 - Alpha",
14+
"Intended Audience :: Developers",
15+
"License :: OSI Approved :: MIT License",
16+
"Natural Language :: English",
17+
"Programming Language :: Python :: 3",
18+
"Programming Language :: Python :: 3.9",
19+
"Programming Language :: Python :: 3.10",
20+
"Programming Language :: Python :: 3.11",
21+
"Topic :: Software Development",
22+
"Topic :: Scientific/Engineering",
23+
"Operating System :: Microsoft :: Windows",
24+
"Operating System :: POSIX",
25+
"Operating System :: Unix",
26+
"Operating System :: MacOS",
27+
]
28+
requires-python = ">=3.9"
29+
dependencies = [
30+
"aws-cdk-lib==2.48.0",
31+
"aws-cdk.aws-lambda-python-alpha",
32+
"constructs>=10.0.0,<11.0.0",
33+
"boto3",
34+
"opensearch-py",
35+
"requests",
36+
"python-jose"
37+
]
38+
39+
[project.optional-dependencies]
40+
dev = [
41+
"pytest==6.2.5",
42+
"moto"
43+
]
44+
45+
[project.urls]
46+
homepage = "https://github.com/IMAP-Science-Operations-Center"
47+
repository = "https://github.com/IMAP-Science-Operations-Center/SDS-in-a-box"
48+
49+
[tool.pytest.ini_options]
50+
testpaths = [
51+
"tests",
52+
]
53+
addopts = "-ra"
54+
markers = [
55+
"network: Test that requires network access",
56+
]

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
requests

sds-access-lib/sds_api.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import requests
2+
import json
3+
import os
4+
import datetime
5+
6+
# THESE MUST BE RESET EVERY TIME FOR NOW
7+
COGNITO_CLIENT_ID = "4rtf569eq2brgk3sq8ek4uqc91"
8+
UPLOAD_API_URL = "https://vo54qpw7fy4uarorbxnplilgae0hhwos.lambda-url.us-west-2.on.aws/"
9+
DOWNLOAD_API_URL = 'https://i5y2mfaoh3capmulqehouzcjya0zwedr.lambda-url.us-west-2.on.aws/'
10+
QUERY_API_URL = 'https://stkjssplyeb5deqgn25wiaix2y0icvzg.lambda-url.us-west-2.on.aws/'
11+
12+
USER_TOKEN = None
13+
LOGIN_TIME = None
14+
15+
def _set_user_token(t):
16+
global USER_TOKEN
17+
global LOGIN_TIME
18+
19+
LOGIN_TIME = datetime.datetime.now()
20+
USER_TOKEN = t
21+
22+
23+
def _get_user_token():
24+
global USER_TOKEN
25+
global LOGIN_TIME
26+
if LOGIN_TIME is None:
27+
print("New login needed. Login is valid for 60 minutes.")
28+
elif (datetime.datetime.now() - LOGIN_TIME).total_seconds() >= 3600:
29+
print("Login expired. Please log in again.")
30+
else:
31+
return USER_TOKEN
32+
33+
t = get_sdc_token()
34+
35+
return t
36+
37+
38+
def get_sdc_token(user_name=None, password=None):
39+
'''
40+
This function authenticates the user. An access token is automatically stored in the USER_TOKEN
41+
variable in this file, and functions will attempt to find a valid user token in that variable.
42+
43+
:param user_name: User's SDC username
44+
:param password: User's SDC password
45+
46+
:return: A string that also gets stored in the USER_TOKEN variable in this file. You don't need this string unless
47+
you plan on making your own API calls, using functions outside of this file.
48+
'''
49+
global COGNITO_CLIENT_ID
50+
if user_name is None:
51+
user_name = input("Username:")
52+
if password is None:
53+
import getpass
54+
password = getpass.getpass("Password for " + user_name + ":")
55+
56+
authentication_url = "https://cognito-idp.us-west-2.amazonaws.com/"
57+
authentication_headers = {'X-Amz-Target': 'AWSCognitoIdentityProviderService.InitiateAuth',
58+
'Content-Type': 'application/x-amz-json-1.1'}
59+
data = json.dumps({"ClientId": COGNITO_CLIENT_ID, "AuthFlow": "USER_PASSWORD_AUTH",
60+
"AuthParameters": {"USERNAME": user_name, "PASSWORD": password}})
61+
62+
# Attempt to grab the SDC token.
63+
try:
64+
token_response = requests.post(authentication_url, data=data, headers=authentication_headers)
65+
t = token_response.json()['AuthenticationResult']['AccessToken']
66+
except KeyError:
67+
print("Invalid username and/or password. Please try again. ")
68+
return
69+
70+
_set_user_token(t)
71+
72+
return t
73+
74+
def _execute_api(url, **kwargs):
75+
token = _get_user_token()
76+
headers = {"Authorization": token}
77+
query_parameters = []
78+
for kw in kwargs:
79+
query_parameters.append(kw + "=" + str(kwargs[kw]))
80+
query_parameters = '&'.join(query_parameters)
81+
url_with_parameters = url + "?" + query_parameters
82+
print(url_with_parameters)
83+
try:
84+
response = requests.get(url_with_parameters, headers=headers)
85+
except Exception as e:
86+
print(f"Could not finish query due to error {str(e)}")
87+
return
88+
return response
89+
90+
def download(filename, download_dir=''):
91+
'''
92+
This function is used to download files from the SDS.
93+
94+
:param filename: The full S3 URI to download
95+
:param download_dir: The directory on the local machine to download the file to.
96+
97+
:return: None, but downloads the file to the specified download directory
98+
'''
99+
global DOWNLOAD_API_URL
100+
download_url = _execute_api(DOWNLOAD_API_URL, s3_uri=filename)
101+
102+
if (download_url.status_code == 400):
103+
print("Not a valid S3 URI. Example input: s3://bucket/path/file.ext")
104+
return
105+
elif (download_url.status_code == 404):
106+
print("No files were found matching the given URI.")
107+
return
108+
109+
file_name_and_path = os.path.join(download_dir, filename[5:])
110+
download_dir = os.path.dirname(file_name_and_path)
111+
if not os.path.exists(download_dir):
112+
os.makedirs(download_dir)
113+
114+
with open(file_name_and_path, 'wb') as file:
115+
print(f"Downloading {file_name_and_path}")
116+
file_location = requests.get(download_url.json()["download_url"])
117+
file.write(file_location.content)
118+
119+
return file_location
120+
121+
def query(**kwargs):
122+
'''
123+
This function is used to query files from the SDS.
124+
There are no required arguments, the search strings will depend on the mission
125+
126+
:return: This returns JSON with all information about the files.
127+
'''
128+
global QUERY_API_URL
129+
response = _execute_api(QUERY_API_URL, **kwargs)
130+
return response.json()
131+
132+
def upload(file_location, file_name, **kwargs):
133+
'''
134+
This function is used to upload files to the SDS.
135+
136+
:param file_location: The path to the file on the local machine to upload to the SDS.
137+
:param file_name: The name of the file you'd like to upload
138+
:param kwargs: Any additional key word arguments passed into this function are stored as tags on the SDS.
139+
140+
:return: This returns a requests response object. If the upload was successful, it'll be code 200.
141+
'''
142+
global UPLOAD_API_URL
143+
response = _execute_api(UPLOAD_API_URL, filename=file_name, **kwargs)
144+
145+
if response.status_code != 200:
146+
print(f"Could not generate an upload URL with the following error: {response.text}")
147+
return
148+
149+
with open(file_location, 'rb') as object_file:
150+
object_text = object_file.read()
151+
response = requests.put(response.json(), data=object_text)
152+
return response
153+
154+
if __name__ == "__main__":
155+
#x = upload(file_location='helloworld.txt', file_name='imap_l0_sci_mag_2024_2.pkts', testing='true')
156+
#print(x)
157+
#x = query(instrument='mag')
158+
#print(x)
159+
x = download("s3://sds-data-harter-asdfasdf/imap/l0/imap_l0_sci_mag_2024_2.pkts")
160+
print(x)

0 commit comments

Comments
 (0)