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

63 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2025-10-14 22:07 +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, "data": None, "error": None} 

32 yaml_location = github.get_snapcraft_yaml_location(gh_owner, gh_repo) 

33 default_branch = github.get_default_branch(gh_owner, gh_repo) 

34 

35 # The snapcraft.yaml is not present 

36 if not yaml_location: 

37 result["data"] = {"default_branch": default_branch} 

38 result["success"] = False 

39 result["error"] = { 

40 "type": "MISSING_YAML_FILE", 

41 "message": ( 

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

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

44 "and runnable." 

45 ), 

46 } 

47 else: 

48 try: 

49 gh_snap_name = github.get_snapcraft_yaml_data( 

50 gh_owner, gh_repo 

51 ).get("name") 

52 

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

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

55 result["success"] = False 

56 result["error"] = { 

57 "type": "SNAP_NAME_DOES_NOT_MATCH", 

58 "message": ( 

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

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

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

62 "snapcraft.yaml to continue." 

63 ), 

64 "yaml_location": yaml_location, 

65 "gh_snap_name": gh_snap_name, 

66 } 

67 except InvalidYAML: 

68 result["success"] = False 

69 result["error"] = { 

70 "type": "INVALID_YAML_FILE", 

71 "message": ( 

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

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

74 ), 

75 } 

76 

77 return result 

78 

79 

80@login_required 

81def get_validate_repo(snap_name): 

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

83 

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

85 

86 return flask.jsonify( 

87 validate_repo( 

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

89 details["snap_name"], 

90 owner, 

91 repo, 

92 ) 

93 ) 

94 

95 

96@login_required 

97def post_build(snap_name): 

98 # Don't allow builds from no contributors 

99 account_snaps = dashboard.get_account_snaps(flask.session) 

100 

101 if snap_name not in account_snaps: 

102 return flask.jsonify( 

103 { 

104 "success": False, 

105 "error": { 

106 "type": "FORBIDDEN", 

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

108 "builds for this snap", 

109 }, 

110 } 

111 ) 

112 

113 try: 

114 if launchpad.is_snap_building(snap_name): 

115 launchpad.cancel_snap_builds(snap_name) 

116 

117 build_id = launchpad.build_snap(snap_name) 

118 

119 except HTTPError as e: 

120 return flask.jsonify( 

121 { 

122 "success": False, 

123 "error": { 

124 "message": "An error happened building " 

125 "this snap, please try again." 

126 }, 

127 "details": e.response.text, 

128 "status_code": e.response.status_code, 

129 } 

130 ) 

131 

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

133 

134 

135@login_required 

136def post_disconnect_repo(snap_name): 

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

138 

139 lp_snap = launchpad.get_snap_by_store_name(snap_name) 

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

141 

142 # Try to remove the GitHub webhook if possible 

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

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

145 

146 try: 

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

148 

149 old_hook = github.get_hook_by_url( 

150 gh_owner, 

151 gh_repo, 

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

153 ) 

154 

155 if old_hook: 

156 github.remove_hook( 

157 gh_owner, 

158 gh_repo, 

159 old_hook["id"], 

160 ) 

161 except HTTPError: 

162 pass 

163 

164 return flask.redirect( 

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

166 )