1
1
import configparser
2
+ import logging
3
+ import os
2
4
import subprocess
3
-
5
+ import sys
4
6
from pathlib import Path
7
+ from typing import Optional , Union
5
8
6
9
import click
7
10
import fire
8
11
9
- from ctfcli .cli .challenges import Challenge
10
- from ctfcli .cli .config import Config
11
- from ctfcli .cli .plugins import Plugins
12
- from ctfcli .cli .templates import Templates
13
- from ctfcli .cli .pages import Pages
14
- from ctfcli .utils .plugins import load_plugins
12
+ from ctfcli .cli .challenges import ChallengeCommand
13
+ from ctfcli .cli .config import ConfigCommand
14
+ from ctfcli .cli .pages import PagesCommand
15
+ from ctfcli .cli .plugins import PluginsCommand
16
+ from ctfcli .cli .templates import TemplatesCommand
17
+ from ctfcli .core .exceptions import ProjectNotInitialized
18
+ from ctfcli .core .plugins import load_plugins
15
19
from ctfcli .utils .git import check_if_dir_is_inside_git_repo
16
20
21
+ # Init logging
22
+ logging .basicConfig (level = os .environ .get ("LOGLEVEL" , "INFO" ).upper ())
23
+
24
+ log = logging .getLogger ("ctfcli.main" )
25
+
26
+
27
+ class CTFCLI :
28
+ @staticmethod
29
+ def init (
30
+ directory : Optional [Union [str , os .PathLike ]] = None ,
31
+ no_git : bool = False ,
32
+ no_commit : bool = False ,
33
+ ):
34
+ log .debug (f"init: (directory={ directory } , no_git={ no_git } , no_commit={ no_commit } )" )
35
+ project_path = Path .cwd ()
17
36
18
- class CTFCLI (object ):
19
- def init (self , directory = None , no_config = False , no_git = False ):
20
- # Create our event directory if requested and use it as our base directory
37
+ # Create our project directory if requested
21
38
if directory :
22
- path = Path (directory )
23
- path .mkdir ()
24
- click .secho (f"Created empty directory in { path .absolute ()} " , fg = "green" )
25
- else :
26
- path = Path ("." )
39
+ project_path = Path (directory )
27
40
28
- # Get variables from user
29
- ctf_url = click .prompt (
30
- "Please enter CTFd instance URL" , default = "" , show_default = False
31
- )
32
- ctf_token = click .prompt (
33
- "Please enter CTFd Admin Access Token" , default = "" , show_default = False
34
- )
35
- # Confirm information with user
36
- if (
37
- click .confirm (f"Do you want to continue with { ctf_url } and { ctf_token } " )
38
- is False
39
- ):
40
- click .echo ("Aborted!" )
41
- return
41
+ if not project_path .exists ():
42
+ project_path .mkdir (parents = True )
43
+ click .secho (f"Created empty directory in { project_path .absolute ()} " , fg = "green" )
42
44
43
45
# Avoid colliding with existing .ctf directory
44
- if (path / ".ctf" ).exists ():
46
+ if (project_path / ".ctf" ).exists ():
45
47
click .secho (".ctf/ folder already exists. Aborting!" , fg = "red" )
46
48
return
47
49
50
+ log .debug (f"project_path: { project_path } " )
51
+
48
52
# Create .ctf directory
49
- (path / ".ctf" ).mkdir ()
53
+ (project_path / ".ctf" ).mkdir ()
54
+
55
+ # Get variables from user
56
+ ctf_url = click .prompt ("Please enter CTFd instance URL" , default = "" , show_default = False )
57
+
58
+ ctf_token = click .prompt ("Please enter CTFd Admin Access Token" , default = "" , show_default = False )
59
+
60
+ # Confirm information with user
61
+ if not click .confirm (f"Do you want to continue with { ctf_url } and { ctf_token } " , default = True ):
62
+ click .echo ("Aborted!" )
63
+ return
50
64
51
65
# Create initial .ctf/config file
52
66
config = configparser .ConfigParser ()
53
67
config ["config" ] = {"url" : ctf_url , "access_token" : ctf_token }
54
68
config ["challenges" ] = {}
55
- with (path / ".ctf" / "config" ).open (mode = "a+" ) as f :
56
- config .write (f )
69
+ with (project_path / ".ctf" / "config" ).open (mode = "a+" ) as config_file :
70
+ config .write (config_file )
57
71
58
- # Create a git repo in the event folder
59
- if check_if_dir_is_inside_git_repo (dir = path .absolute ()) is True :
60
- click .secho ("Already in git repo. Skipping git init." , fg = "yellow" )
61
- elif no_git is True :
72
+ # if git init is to be skipped we can return
73
+ if no_git :
62
74
click .secho ("Skipping git init." , fg = "yellow" )
63
- else :
64
- click .secho (f"Creating git repo in { path .absolute ()} " , fg = "green" )
65
- subprocess .call (["git" , "init" , str (path )])
75
+ return
76
+
77
+ # also skip git init if git is already initialized
78
+ if check_if_dir_is_inside_git_repo (cwd = project_path ):
79
+ click .secho ("Already in a git repo. Skipping git init." , fg = "yellow" )
80
+
81
+ # is git commit is to be skipped we can return
82
+ if no_commit :
83
+ click .secho ("Skipping git commit." , fg = "yellow" )
84
+ return
85
+
86
+ subprocess .call (["git" , "add" , ".ctf/config" ], cwd = project_path )
87
+ subprocess .call (["git" , "commit" , "-m" , "init ctfcli project" ], cwd = project_path )
88
+ return
89
+
90
+ # Create a git repo in the project folder
91
+ click .secho (f"Creating a git repo in { project_path } " , fg = "green" )
92
+ subprocess .call (["git" , "init" , str (project_path )])
93
+
94
+ if no_commit :
95
+ click .secho ("Skipping git commit." , fg = "yellow" )
96
+ return
97
+
98
+ subprocess .call (["git" , "add" , ".ctf/config" ], cwd = project_path )
99
+ subprocess .call (["git" , "commit" , "-m" , "init ctfcli project" ], cwd = project_path )
66
100
67
101
def config (self ):
68
102
return COMMANDS .get ("config" )
@@ -81,21 +115,38 @@ def templates(self):
81
115
82
116
83
117
COMMANDS = {
84
- "challenge" : Challenge (),
85
- "config" : Config (),
86
- "pages" : Pages (),
87
- "plugins" : Plugins (),
88
- "templates" : Templates (),
118
+ "challenge" : ChallengeCommand (),
119
+ "config" : ConfigCommand (),
120
+ "pages" : PagesCommand (),
121
+ "plugins" : PluginsCommand (),
122
+ "templates" : TemplatesCommand (),
89
123
"cli" : CTFCLI (),
90
124
}
91
125
92
126
93
127
def main ():
94
- # load plugins
128
+ # Load plugins
95
129
load_plugins (COMMANDS )
96
130
97
131
# Load CLI
98
- fire .Fire (CTFCLI )
132
+ try :
133
+ # if the command returns an int, then we serialize it as none to prevent fire from printing it
134
+ # (this does not change the actual return value, so it's still good to use as an exit code)
135
+ # everything else is returned as is, so fire can print help messages
136
+ ret = fire .Fire (CTFCLI , serialize = lambda r : None if isinstance (r , int ) else r )
137
+
138
+ if isinstance (ret , int ):
139
+ sys .exit (ret )
140
+
141
+ except ProjectNotInitialized :
142
+ if click .confirm (
143
+ "Outside of a ctfcli project, would you like to start a new project in this directory?" ,
144
+ default = False ,
145
+ ):
146
+ CTFCLI .init ()
147
+ except KeyboardInterrupt :
148
+ click .secho ("\n [Ctrl-C] Aborting." , fg = "red" )
149
+ sys .exit (2 )
99
150
100
151
101
152
if __name__ == "__main__" :
0 commit comments