Coverage for csaf/cli.py: 81.94%

48 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-04 16:28:45 +00:00

1"""Commandline API gateway for csaf.""" 

2 

3import sys 

4from typing import List, Mapping 

5 

6import typer 

7 

8import csaf 

9import csaf.config as cfg 

10import csaf.csaf as lint 

11import csaf.env as env 

12from csaf import log 

13 

14app = typer.Typer( 

15 add_completion=False, 

16 context_settings={'help_option_names': ['-h', '--help']}, 

17 no_args_is_help=True, 

18) 

19 

20 

21@app.callback(invoke_without_command=True) 

22def callback( 

23 version: bool = typer.Option( 

24 False, 

25 '-V', 

26 '--version', 

27 help='Display the csaf version and exit', 

28 is_eager=True, 

29 ) 

30) -> None: 

31 """ 

32 Common Security Advisory Framework (CSAF) Verification and Validation. 

33 """ 

34 if version: 

35 typer.echo(f'{csaf.APP_NAME} version {csaf.__version__}') 

36 raise typer.Exit() 

37 

38 

39@app.command('template') 

40def app_template() -> int: 

41 """ 

42 Write a template of a well-formed JSON configuration to standard out and exit 

43 

44 The strategy for looking up configurations is to start at the current working 

45 directory trying to read a file with the name `.csaf.json` else try to read 

46 same named file in the user folder (home). 

47 

48 In case an explicit path is given to the config option of commands that offer 

49 it, only that path is considered. 

50 """ 

51 sys.stdout.write(cfg.generate_template()) 

52 return sys.exit(0) 

53 

54 

55@app.command('report') 

56def report() -> int: 

57 """Output the report of the environment for support.""" 

58 sys.stdout.write(env.report()) 

59 return sys.exit(0) 

60 

61 

62@app.command('validate') 

63def validate( 

64 source: List[str], 

65 inp: str = typer.Option( 

66 '', 

67 '-i', 

68 '--input', 

69 help='Path to CSAF input file', 

70 metavar='<sourcepath>', 

71 ), 

72 conf: str = typer.Option( 

73 '', 

74 '-c', 

75 '--config', 

76 help=f'Path to config file (default is $HOME/{csaf.DEFAULT_CONFIG_NAME})', 

77 metavar='<configpath>', 

78 ), 

79 bail_out: bool = typer.Option( 

80 False, 

81 '-b', 

82 '--bail-out', 

83 help='Bail out (exit) on first failure (default is False)', 

84 ), 

85 verify: bool = typer.Option( 

86 False, 

87 '-n', 

88 '--dry-run', 

89 help='Dry run (default is False)', 

90 ), 

91 verbose: bool = typer.Option( 

92 False, 

93 '-v', 

94 '--verbose', 

95 help='Verbose output (default is False)', 

96 ), 

97 quiet: bool = typer.Option( 

98 False, 

99 '-q', 

100 '--quiet', 

101 help='Minimal output (default is False)', 

102 ), 

103 strict: bool = typer.Option( 

104 False, 

105 '-s', 

106 '--strict', 

107 help='Ouput noisy warnings on console (default is False)', 

108 ), 

109) -> int: 

110 """ 

111 Common Security Advisory Framework (CSAF) Verification and Validation. 

112 

113 You can set some options per environment variables: 

114 

115 \b 

116 * CSAF_USER='remote-user' 

117 * CSAF_TOKEN='remote-secret' 

118 * CSAF_BASE_URL='https://csaf.example.com/file/names/below/here/' 

119 * CSAF_BAIL_OUT='AnythingTruthy' 

120 * CSAF_DEBUG='AnythingTruthy' 

121 * CSAF_VERBOSE='AnythingTruthy' 

122 * CSAF_STRICT='AnythingTruthy' 

123 

124 The quiet option (if given) disables any conflicting verbosity setting. 

125 """ 

126 command = 'validate' 

127 transaction_mode = 'commit' if not verify else 'dry-run' 

128 if quiet: 128 ↛ 132line 128 didn't jump to line 132, because the condition on line 128 was never false

129 csaf.QUIET = True 

130 csaf.DEBUG = False 

131 csaf.VERBOSE = False 

132 elif verbose: 

133 csaf.VERBOSE = True 

134 

135 if strict: 135 ↛ 138line 135 didn't jump to line 138, because the condition on line 135 was never false

136 csaf.STRICT = True 

137 

138 if bail_out: 138 ↛ 141line 138 didn't jump to line 141, because the condition on line 138 was never false

139 csaf.BAIL_OUT = True 

140 

141 if transaction_mode == 'dry-run': 141 ↛ 144line 141 didn't jump to line 144, because the condition on line 141 was never false

142 csaf.DRY_RUN = True 

143 

144 configuration = cfg.read_configuration(str(conf)) if conf else {} 

145 

146 options: Mapping[str, object] = { 

147 'configuration': configuration, 

148 'bail_out': bail_out, 

149 'quiet': quiet, 

150 'strict': strict, 

151 'verbose': verbose, 

152 } 

153 

154 paths = (inp,) if inp else tuple(source) 

155 code, message = lint.process(command, transaction_mode, paths[0], options) 

156 if message: 156 ↛ 158line 156 didn't jump to line 158, because the condition on line 156 was never false

157 log.error(message) 

158 return code 

159 

160 

161@app.command('version') 

162def app_version() -> None: 

163 """ 

164 Display the csaf version and exit. 

165 """ 

166 callback(True)