Skip to content

Commit c0b7957

Browse files
authored
Merge pull request #1 from pythonkr/feature/setup-repository
Django 프로젝트 및 CD 세팅
2 parents b9a44c5 + 713f19f commit c0b7957

37 files changed

Lines changed: 3848 additions & 0 deletions

.github/scripts/get_new_version.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""
2+
Description:
3+
- Print the calculated new version.
4+
5+
Usage:
6+
- python3 update_version.py --current-version <version> (--stage)
7+
8+
Version Scheme:
9+
- <year>.<month>.<release count>(a<prerelease count>)
10+
11+
example:
12+
- case 1:
13+
- given:
14+
- current version: 2025.1.1
15+
- today : YEAR = 2025, MONTH = 1
16+
- then:
17+
- if stage is false:
18+
- new version: 2025.1.2
19+
- if stage is true:
20+
- new version: 2025.1.2a0
21+
- case 2:
22+
- given:
23+
- current version: 2025.1.2a0
24+
- today : YEAR = 2025, MONTH = 1
25+
- then:
26+
- if stage is false:
27+
- new version: 2025.1.2
28+
- if stage is true:
29+
- new version: 2025.1.2a1
30+
- case 3:
31+
- given:
32+
- current version: 2025.1.1
33+
- today : YEAR = 2025, MONTH = 2
34+
- then:
35+
- if stage is false:
36+
- new version: 2025.2.0
37+
- if stage is true:
38+
- new version: 2025.2.0a0
39+
"""
40+
41+
import argparse
42+
import datetime
43+
import typing
44+
45+
import packaging.version
46+
47+
PreType = tuple[typing.Literal["a", "b", "rc"], int] | None
48+
49+
50+
class ArgumentNamespace(argparse.Namespace):
51+
current: str
52+
stage: bool = False
53+
54+
55+
def increment_version_count(version: packaging.version.Version, is_stage: bool) -> str:
56+
if (current_pre := version.pre) and current_pre[0] != "a":
57+
raise ValueError(f"Unsupported pre-release version: {current_pre[0]}")
58+
59+
# Get the current date
60+
today: datetime.date = datetime.date.today()
61+
62+
# Calculate the new version
63+
new_count: int = 0
64+
if version.major == today.year and version.minor == today.month:
65+
if current_pre:
66+
# If the current version is a pre-release, do not increment the count
67+
new_count = version.micro
68+
else:
69+
# Same month, increment the count
70+
new_count = version.micro + 1
71+
else:
72+
# Different month, reset the count
73+
new_count = 1
74+
current_pre = None
75+
76+
new_pre: PreType = ((current_pre[0], current_pre[1] + 1) if current_pre else ("a", 0)) if is_stage else None
77+
new_pre_str = f"{new_pre[0]}{new_pre[1]}" if new_pre else ""
78+
return f"{today.year}.{today.month}.{new_count}{new_pre_str}"
79+
80+
81+
if __name__ == "__main__":
82+
parser = argparse.ArgumentParser(description="Update version in files.")
83+
parser.add_argument("--current", type=str, required=True)
84+
parser.add_argument("--stage", default=False, action="store_true")
85+
86+
args = parser.parse_args(namespace=ArgumentNamespace())
87+
print(increment_version_count(packaging.version.parse(args.current), args.stage))
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import argparse
2+
import json
3+
import pathlib
4+
import typing
5+
6+
import boto3
7+
8+
if typing.TYPE_CHECKING:
9+
import mypy_boto3_ssm
10+
11+
ssm_client: "mypy_boto3_ssm.SSMClient" = boto3.client("ssm")
12+
13+
14+
class ValueDiff(typing.NamedTuple):
15+
old: str | None
16+
new: str | None
17+
18+
19+
class ParameterDiffCollection(typing.NamedTuple):
20+
updated: dict[str, ValueDiff]
21+
created: dict[str, ValueDiff]
22+
deleted: dict[str, ValueDiff]
23+
24+
25+
def read_json_file(json_file: pathlib.Path, stage: str) -> dict[str, str]:
26+
if not (data_groups := json.loads(json_file.read_text())) or not isinstance(data_groups, dict):
27+
raise ValueError("JSON 파일이 잘못되었습니다.")
28+
29+
if not (data := typing.cast(dict[str, typing.Any], data_groups.get(stage))):
30+
raise ValueError("JSON 파일에 해당 스테이지의 파라미터가 없습니다.")
31+
32+
if not all(isinstance(k, str) and isinstance(v, (str, int, float, bool)) for k, v in data.items()):
33+
# object / array / null is not allowed here
34+
raise ValueError("JSON 파일의 파라미터가 잘못되었습니다.")
35+
36+
return {k: str(v) for k, v in data.items()}
37+
38+
39+
def read_parameter_store(project_name: str, stage: str) -> dict[str, str]:
40+
parameters: dict[str, str] = {}
41+
next_token = "" # nosec: B105
42+
while next_token is not None:
43+
result = ssm_client.get_parameters_by_path(
44+
Path=f"/{project_name}/{stage}",
45+
MaxResults=10,
46+
**({"NextToken": next_token} if next_token else {}),
47+
)
48+
parameters.update({p["Name"].split("/")[-1]: p["Value"] for p in result["Parameters"]})
49+
next_token = result.get("NextToken")
50+
return parameters
51+
52+
53+
def get_parameter_diff(old_parameters: dict[str, str], new_parameters: dict[str, str]) -> ParameterDiffCollection:
54+
created, updated, deleted = {}, {}, {}
55+
56+
for fields in old_parameters.keys() | new_parameters.keys():
57+
value = ValueDiff(old=old_parameters.get(fields), new=new_parameters.get(fields))
58+
if value.old != value.new:
59+
if value.old is None:
60+
created[fields] = value
61+
elif value.new is None:
62+
deleted[fields] = value
63+
else:
64+
updated[fields] = value
65+
66+
return ParameterDiffCollection(updated=updated, created=created, deleted=deleted)
67+
68+
69+
def update_parameter_store(project_name: str, stage: str, diff: ParameterDiffCollection) -> None:
70+
for field, values in {**diff.created, **diff.updated}.items():
71+
ssm_client.put_parameter(
72+
Name=f"/{project_name}/{stage}/{field}",
73+
Value=values.new,
74+
Type="String",
75+
Overwrite=True,
76+
)
77+
78+
if diff.deleted:
79+
ssm_client.delete_parameters(Names=[f"/{project_name}/{stage}/{field}" for field in diff.deleted.keys()])
80+
81+
82+
def main(project_name: str, stage: str, json_file: pathlib.Path) -> None:
83+
if not all([json_file.is_file(), project_name, stage]):
84+
raise ValueError("인자를 확인해주세요.")
85+
86+
old_params = read_parameter_store(project_name, stage)
87+
new_params = read_json_file(json_file, stage)
88+
diff = get_parameter_diff(old_params, new_params)
89+
90+
print(f"Updated: '{', '.join(diff.updated.keys())}'")
91+
print(f"Created: '{', '.join(diff.created.keys())}'")
92+
print(f"Deleted: '{', '.join(diff.deleted.keys())}'")
93+
update_parameter_store(project_name, stage, diff)
94+
95+
96+
if __name__ == "__main__":
97+
parser = argparse.ArgumentParser(description=__doc__)
98+
parser.add_argument("--project_name", type=str)
99+
parser.add_argument("--stage", type=str)
100+
parser.add_argument("--json_file", type=pathlib.Path)
101+
102+
args = parser.parse_args()
103+
main(project_name=args.project_name, stage=args.stage, json_file=args.json_file)

.github/workflows/lint.yaml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Check lint
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- 'main'
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
lint:
15+
name: Run lint
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout source codes
19+
uses: actions/checkout@v4
20+
21+
- uses: actions/setup-python@v4
22+
with:
23+
python-version: '3.12'
24+
25+
- name: Install dependencies
26+
run: pip install 'pre-commit'
27+
28+
- name: cache pre-commit repo
29+
uses: actions/cache@v4
30+
with:
31+
path: ~/.cache/pre-commit
32+
key: ${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
33+
34+
- name: Run pre-commit
35+
id: run-pre-commit
36+
run: pre-commit run --all-files --show-diff-on-failure

0 commit comments

Comments
 (0)