Coverage for cli/commands/webhooks_cmd.py: 94.74%

102 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-07-30 21:22 +0000

1"""Webhook commands.""" 

2 

3import sys 

4from typing import Any 

5 

6import click 

7 

8from ..config import GCOConfig 

9from ..output import get_output_formatter 

10 

11pass_config = click.make_pass_decorator(GCOConfig, ensure=True) 

12 

13 

14@click.group() 

15@pass_config 

16def webhooks(config: Any) -> None: 

17 """Manage webhooks for job event notifications. 

18 

19 Webhooks receive HTTP POST notifications when job events occur 

20 (job.started, job.completed, job.failed). 

21 """ 

22 pass 

23 

24 

25@webhooks.command("list") 

26@click.option("--namespace", "-n", help="Filter by namespace") 

27@click.option("--region", "-r", help="Region to query (any region works)") 

28@pass_config 

29def webhooks_list(config: Any, namespace: Any, region: Any) -> None: 

30 """List all registered webhooks. 

31 

32 Examples: 

33 gco webhooks list 

34 gco webhooks list --namespace gco-jobs 

35 """ 

36 formatter = get_output_formatter(config) 

37 

38 try: 

39 from ..aws_client import get_aws_client 

40 

41 aws_client = get_aws_client(config) 

42 

43 query_region = region or (config.default_region if config.use_regional_api else None) 

44 params = {} 

45 if namespace: 

46 params["namespace"] = namespace 

47 

48 result = aws_client.call_api( 

49 method="GET", 

50 path="/api/v1/webhooks", 

51 region=query_region, 

52 params=params, 

53 ) 

54 

55 if config.output_format == "table": 55 ↛ 74line 55 didn't jump to line 74 because the condition on line 55 was always true

56 webhooks_data = result.get("webhooks", []) 

57 if not webhooks_data: 

58 formatter.print_info("No webhooks found") 

59 return 

60 

61 print(f"\n Webhooks ({result.get('count', 0)} total)") 

62 print(" " + "-" * 80) 

63 print( 

64 " ID URL EVENTS NAMESPACE" 

65 ) 

66 print(" " + "-" * 80) 

67 for w in webhooks_data: 

68 wid = w.get("id", "")[:8] 

69 url = w.get("url", "")[:40] 

70 events = ",".join(w.get("events", []))[:18] 

71 ns = (w.get("namespace") or "all")[:12] 

72 print(f" {wid:<9} {url:<42} {events:<19} {ns}") 

73 else: 

74 formatter.print(result) 

75 

76 except Exception as e: 

77 formatter.print_error(f"Failed to list webhooks: {e}") 

78 sys.exit(1) 

79 

80 

81@webhooks.command("get") 

82@click.argument("webhook_id") 

83@click.option("--region", "-r", help="Region to query (any region works)") 

84@pass_config 

85def webhooks_get(config: Any, webhook_id: Any, region: Any) -> None: 

86 """Get a single webhook by id. 

87 

88 The webhooks API exposes list/create/delete but no fetch-by-id endpoint, so 

89 this lists the region's webhooks and returns the one whose id matches. 

90 

91 Examples: 

92 gco webhooks get abc12345 

93 gco webhooks get abc12345 -r us-east-1 

94 """ 

95 formatter = get_output_formatter(config) 

96 

97 try: 

98 from ..aws_client import get_aws_client 

99 

100 aws_client = get_aws_client(config) 

101 

102 query_region = region or (config.default_region if config.use_regional_api else None) 

103 result = aws_client.call_api( 

104 method="GET", 

105 path="/api/v1/webhooks", 

106 region=query_region, 

107 ) 

108 match = next( 

109 (w for w in result.get("webhooks", []) if w.get("id") == webhook_id), 

110 None, 

111 ) 

112 if match is None: 112 ↛ 115line 112 didn't jump to line 115 because the condition on line 112 was always true

113 formatter.print_error(f"Webhook '{webhook_id}' not found") 

114 sys.exit(1) 

115 formatter.print(match) 

116 

117 except SystemExit: 

118 raise 

119 except Exception as e: 

120 formatter.print_error(f"Failed to get webhook: {e}") 

121 sys.exit(1) 

122 

123 

124@webhooks.command("create") 

125@click.option("--url", "-u", required=True, help="Webhook URL") 

126@click.option( 

127 "--event", 

128 "-e", 

129 multiple=True, 

130 required=True, 

131 type=click.Choice(["job.started", "job.completed", "job.failed"]), 

132 help="Events to subscribe to", 

133) 

134@click.option("--namespace", "-n", help="Filter events by namespace") 

135@click.option("--secret", "-s", help="Secret for HMAC signature verification") 

136@click.option("--region", "-r", help="Region to use (any region works)") 

137@pass_config 

138def webhooks_create( 

139 config: Any, url: Any, event: Any, namespace: Any, secret: Any, region: Any 

140) -> None: 

141 """Register a new webhook for job events. 

142 

143 Examples: 

144 gco webhooks create --url https://example.com/webhook -e job.completed -e job.failed 

145 gco webhooks create -u https://slack.com/webhook -e job.failed -n gco-jobs 

146 """ 

147 formatter = get_output_formatter(config) 

148 

149 try: 

150 from ..aws_client import get_aws_client 

151 

152 aws_client = get_aws_client(config) 

153 

154 query_region = region or (config.default_region if config.use_regional_api else None) 

155 result = aws_client.call_api( 

156 method="POST", 

157 path="/api/v1/webhooks", 

158 region=query_region, 

159 body={ 

160 "url": url, 

161 "events": list(event), 

162 "namespace": namespace, 

163 "secret": secret, 

164 }, 

165 ) 

166 

167 formatter.print_success("Webhook registered successfully") 

168 formatter.print(result) 

169 

170 except Exception as e: 

171 formatter.print_error(f"Failed to create webhook: {e}") 

172 sys.exit(1) 

173 

174 

175@webhooks.command("delete") 

176@click.argument("webhook_id") 

177@click.option("--region", "-r", help="Region to use (any region works)") 

178@click.option("--yes", "-y", is_flag=True, help="Skip confirmation") 

179@pass_config 

180def webhooks_delete(config: Any, webhook_id: Any, region: Any, yes: Any) -> None: 

181 """Delete a webhook. 

182 

183 Examples: 

184 gco webhooks delete abc123 

185 gco webhooks delete abc123 -y 

186 """ 

187 formatter = get_output_formatter(config) 

188 

189 if not yes: 189 ↛ 190line 189 didn't jump to line 190 because the condition on line 189 was never true

190 click.confirm(f"Delete webhook '{webhook_id}'?", abort=True) 

191 

192 try: 

193 from ..aws_client import get_aws_client 

194 

195 aws_client = get_aws_client(config) 

196 

197 query_region = region or (config.default_region if config.use_regional_api else None) 

198 result = aws_client.call_api( 

199 method="DELETE", 

200 path=f"/api/v1/webhooks/{webhook_id}", 

201 region=query_region, 

202 ) 

203 

204 formatter.print_success(f"Webhook '{webhook_id}' deleted") 

205 formatter.print(result) 

206 

207 except Exception as e: 

208 formatter.print_error(f"Failed to delete webhook: {e}") 

209 sys.exit(1)