Coverage for webapp/endpoints/publisher/builds.py: 49%

61 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2025-08-26 22:06 +0000

1# Standard library 

2import os 

3 

4# Packages 

5import flask 

6from canonicalwebteam.store_api.dashboard import Dashboard 

7 

8from requests.exceptions import HTTPError 

9 

10# Local 

11from webapp.helpers import api_publisher_session, launchpad 

12from webapp.api.github import GitHub, InvalidYAML 

13from webapp.decorators import login_required 

14 

15GITHUB_WEBHOOK_HOST_URL = os.getenv("GITHUB_WEBHOOK_HOST_URL") 

16 

17dashboard = Dashboard(api_publisher_session) 

18 

19 

20@login_required 

21def get_snap_build_page(snap_name, build_id): 

22 # If this fails, the page will 404 

23 dashboard.get_snap_info(flask.session, snap_name) 

24 return flask.render_template( 

25 "store/publisher.html", snap_name=snap_name, build_id=build_id 

26 ) 

27 

28 

29def validate_repo(github_token, snap_name, gh_owner, gh_repo): 

30 github = GitHub(github_token) 

31 result = {"success": True} 

32 yaml_location = github.get_snapcraft_yaml_location(gh_owner, gh_repo) 

33 

34 # The snapcraft.yaml is not present 

35 if not yaml_location: 

36 result["success"] = False 

37 result["error"] = { 

38 "type": "MISSING_YAML_FILE", 

39 "message": ( 

40 "Missing snapcraft.yaml: this repo needs a snapcraft.yaml " 

41 "file, so that Snapcraft can make it buildable, installable " 

42 "and runnable." 

43 ), 

44 } 

45 else: 

46 try: 

47 gh_snap_name = github.get_snapcraft_yaml_data( 

48 gh_owner, gh_repo 

49 ).get("name") 

50 

51 # The property name inside the yaml file doesn't match the snap 

52 if gh_snap_name is not None and gh_snap_name != snap_name: 

53 result["success"] = False 

54 result["error"] = { 

55 "type": "SNAP_NAME_DOES_NOT_MATCH", 

56 "message": ( 

57 "Name mismatch: the snapcraft.yaml uses the snap " 

58 f'name "{gh_snap_name}", but you\'ve registered' 

59 f' the name "{snap_name}". Update your ' 

60 "snapcraft.yaml to continue." 

61 ), 

62 "yaml_location": yaml_location, 

63 "gh_snap_name": gh_snap_name, 

64 } 

65 except InvalidYAML: 

66 result["success"] = False 

67 result["error"] = { 

68 "type": "INVALID_YAML_FILE", 

69 "message": ( 

70 "Invalid snapcraft.yaml: there was an issue parsing the " 

71 f"snapcraft.yaml for {snap_name}." 

72 ), 

73 } 

74 

75 return result 

76 

77 

78@login_required 

79def get_validate_repo(snap_name): 

80 details = dashboard.get_snap_info(flask.session, snap_name) 

81 

82 owner, repo = flask.request.args.get("repo").split("/") 

83 

84 return flask.jsonify( 

85 validate_repo( 

86 flask.session.get("github_auth_secret"), 

87 details["snap_name"], 

88 owner, 

89 repo, 

90 ) 

91 ) 

92 

93 

94@login_required 

95def post_build(snap_name): 

96 # Don't allow builds from no contributors 

97 account_snaps = dashboard.get_account_snaps(flask.session) 

98 

99 if snap_name not in account_snaps: 

100 return flask.jsonify( 

101 { 

102 "success": False, 

103 "error": { 

104 "type": "FORBIDDEN", 

105 "message": "You are not allowed to request " 

106 "builds for this snap", 

107 }, 

108 } 

109 ) 

110 

111 try: 

112 if launchpad.is_snap_building(snap_name): 

113 launchpad.cancel_snap_builds(snap_name) 

114 

115 build_id = launchpad.build_snap(snap_name) 

116 

117 except HTTPError as e: 

118 return flask.jsonify( 

119 { 

120 "success": False, 

121 "error": { 

122 "message": "An error happened building " 

123 "this snap, please try again." 

124 }, 

125 "details": e.response.text, 

126 "status_code": e.response.status_code, 

127 } 

128 ) 

129 

130 return flask.jsonify({"success": True, "build_id": build_id}) 

131 

132 

133@login_required 

134def post_disconnect_repo(snap_name): 

135 details = dashboard.get_snap_info(flask.session, snap_name) 

136 

137 lp_snap = launchpad.get_snap_by_store_name(snap_name) 

138 launchpad.delete_snap(details["snap_name"]) 

139 

140 # Try to remove the GitHub webhook if possible 

141 if flask.session.get("github_auth_secret"): 

142 github = GitHub(flask.session.get("github_auth_secret")) 

143 

144 try: 

145 gh_owner, gh_repo = lp_snap["git_repository_url"][19:].split("/") 

146 

147 old_hook = github.get_hook_by_url( 

148 gh_owner, 

149 gh_repo, 

150 f"{GITHUB_WEBHOOK_HOST_URL}api/{snap_name}/webhook/notify", 

151 ) 

152 

153 if old_hook: 

154 github.remove_hook( 

155 gh_owner, 

156 gh_repo, 

157 old_hook["id"], 

158 ) 

159 except HTTPError: 

160 pass 

161 

162 return flask.redirect( 

163 flask.url_for(".get_snap_builds", snap_name=snap_name) 

164 )