1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.linecorp.centraldogma.server.internal.api;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.collect.ImmutableList.toImmutableList;
21 import static com.linecorp.centraldogma.server.internal.mirror.DefaultMirroringServicePlugin.mirrorConfig;
22 import static com.linecorp.centraldogma.server.internal.storage.repository.DefaultMetaRepository.mirrorFile;
23
24 import java.net.URI;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.CompletableFuture;
28 import java.util.concurrent.TimeUnit;
29
30 import javax.annotation.Nullable;
31
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import com.cronutils.model.Cron;
36 import com.google.common.collect.ImmutableList;
37 import com.google.common.collect.ImmutableMap;
38
39 import com.linecorp.armeria.server.annotation.ConsumesJson;
40 import com.linecorp.armeria.server.annotation.Delete;
41 import com.linecorp.armeria.server.annotation.Get;
42 import com.linecorp.armeria.server.annotation.Param;
43 import com.linecorp.armeria.server.annotation.Post;
44 import com.linecorp.armeria.server.annotation.ProducesJson;
45 import com.linecorp.armeria.server.annotation.Put;
46 import com.linecorp.armeria.server.annotation.StatusCode;
47 import com.linecorp.armeria.server.annotation.decorator.RequestTimeout;
48 import com.linecorp.centraldogma.common.Author;
49 import com.linecorp.centraldogma.common.Change;
50 import com.linecorp.centraldogma.common.Markup;
51 import com.linecorp.centraldogma.common.ProjectRole;
52 import com.linecorp.centraldogma.common.RepositoryRole;
53 import com.linecorp.centraldogma.common.Revision;
54 import com.linecorp.centraldogma.internal.api.v1.MirrorDto;
55 import com.linecorp.centraldogma.internal.api.v1.MirrorRequest;
56 import com.linecorp.centraldogma.internal.api.v1.PushResultDto;
57 import com.linecorp.centraldogma.server.CentralDogmaConfig;
58 import com.linecorp.centraldogma.server.ZoneConfig;
59 import com.linecorp.centraldogma.server.command.Command;
60 import com.linecorp.centraldogma.server.command.CommandExecutor;
61 import com.linecorp.centraldogma.server.command.CommitResult;
62 import com.linecorp.centraldogma.server.internal.api.auth.RequiresProjectRole;
63 import com.linecorp.centraldogma.server.internal.api.auth.RequiresRepositoryRole;
64 import com.linecorp.centraldogma.server.internal.mirror.MirrorRunner;
65 import com.linecorp.centraldogma.server.internal.mirror.MirrorSchedulingService;
66 import com.linecorp.centraldogma.server.internal.storage.project.ProjectApiManager;
67 import com.linecorp.centraldogma.server.metadata.User;
68 import com.linecorp.centraldogma.server.mirror.Mirror;
69 import com.linecorp.centraldogma.server.mirror.MirrorAccessController;
70 import com.linecorp.centraldogma.server.mirror.MirrorListener;
71 import com.linecorp.centraldogma.server.mirror.MirrorResult;
72 import com.linecorp.centraldogma.server.mirror.MirroringServicePluginConfig;
73 import com.linecorp.centraldogma.server.storage.repository.MetaRepository;
74 import com.linecorp.centraldogma.server.storage.repository.Repository;
75
76
77
78
79 @ProducesJson
80 public class MirroringServiceV1 extends AbstractService {
81
82 private static final Logger logger = LoggerFactory.getLogger(MirroringServiceV1.class);
83
84
85
86
87
88 private final ProjectApiManager projectApiManager;
89 private final MirrorRunner mirrorRunner;
90 private final Map<String, Object> mirrorZoneConfig;
91 @Nullable
92 private final ZoneConfig zoneConfig;
93 private final MirrorAccessController accessController;
94
95 public MirroringServiceV1(ProjectApiManager projectApiManager, CommandExecutor executor,
96 MirrorRunner mirrorRunner, CentralDogmaConfig config,
97 MirrorAccessController accessController) {
98 super(executor);
99 this.projectApiManager = projectApiManager;
100 this.mirrorRunner = mirrorRunner;
101 zoneConfig = config.zone();
102 mirrorZoneConfig = mirrorZoneConfig(config);
103 this.accessController = accessController;
104 }
105
106 private static Map<String, Object> mirrorZoneConfig(CentralDogmaConfig config) {
107 final MirroringServicePluginConfig mirrorConfig = mirrorConfig(config);
108 final ImmutableMap.Builder<String, Object> builder = ImmutableMap.builderWithExpectedSize(2);
109 final boolean zonePinned = mirrorConfig != null && mirrorConfig.zonePinned();
110 builder.put("zonePinned", zonePinned);
111 final ZoneConfig zone = config.zone();
112 if (zone != null) {
113 builder.put("zone", zone);
114 }
115 return builder.build();
116 }
117
118
119
120
121
122
123 @RequiresProjectRole(ProjectRole.OWNER)
124 @Get("/projects/{projectName}/mirrors")
125 public CompletableFuture<List<MirrorDto>> listProjectMirrors(@Param String projectName) {
126 final CompletableFuture<List<Mirror>> future = metaRepo(projectName).mirrors(true);
127 return convertToMirrorDtos(projectName, future);
128 }
129
130
131
132
133
134
135 @RequiresRepositoryRole(RepositoryRole.ADMIN)
136 @Get("/projects/{projectName}/repos/{repoName}/mirrors")
137 public CompletableFuture<List<MirrorDto>> listRepoMirrors(@Param String projectName,
138 Repository repository) {
139 final CompletableFuture<List<Mirror>> future = metaRepo(projectName).mirrors(repository.name(), true);
140 return convertToMirrorDtos(projectName, future);
141 }
142
143
144
145
146
147
148 @RequiresRepositoryRole(RepositoryRole.ADMIN)
149 @Get("/projects/{projectName}/repos/{repoName}/mirrors/{id}")
150 public CompletableFuture<MirrorDto> getMirror(@Param String projectName,
151 Repository repository,
152 @Param String id) {
153 return metaRepo(projectName).mirror(repository.name(), id).thenCompose(mirror -> {
154 return accessController.isAllowed(mirror.remoteRepoUri()).thenApply(allowed -> {
155 return convertToMirrorDto(projectName, mirror, allowed);
156 });
157 });
158 }
159
160
161
162
163
164
165 @Post("/projects/{projectName}/repos/{repoName}/mirrors")
166 @ConsumesJson
167 @StatusCode(201)
168 @RequiresRepositoryRole(RepositoryRole.ADMIN)
169 public CompletableFuture<PushResultDto> createMirror(@Param String projectName,
170 Repository repository,
171 MirrorRequest newMirror,
172 Author author, User user) {
173 return createOrUpdate(projectName, repository.name(), newMirror, author, user, false);
174 }
175
176
177
178
179
180
181 @ConsumesJson
182 @Put("/projects/{projectName}/repos/{repoName}/mirrors/{id}")
183 @RequiresRepositoryRole(RepositoryRole.ADMIN)
184 public CompletableFuture<PushResultDto> updateMirror(@Param String projectName,
185 Repository repository,
186 MirrorRequest mirror,
187 @Param String id, Author author, User user) {
188 checkArgument(id.equals(mirror.id()), "The mirror ID (%s) can't be updated", id);
189 return createOrUpdate(projectName, repository.name(), mirror, author, user, true);
190 }
191
192
193
194
195
196
197 @Delete("/projects/{projectName}/repos/{repoName}/mirrors/{id}")
198 @RequiresRepositoryRole(RepositoryRole.ADMIN)
199 public CompletableFuture<Void> deleteMirror(@Param String projectName,
200 Repository repository,
201 @Param String id, Author author) {
202 final MetaRepository metaRepository = metaRepo(projectName);
203 final String repoName = repository.name();
204 return metaRepository.mirror(repoName, id).thenCompose(mirror -> {
205
206 final Command<CommitResult> command =
207 Command.push(author, projectName, metaRepository.name(),
208 Revision.HEAD, "Delete mirror: " + id + " in " + repoName, "",
209 Markup.PLAINTEXT, Change.ofRemoval(mirrorFile(repoName, id)));
210 return executor().execute(command).thenApply(result -> null);
211 });
212 }
213
214 private CompletableFuture<PushResultDto> createOrUpdate(
215 String projectName, String repoName, MirrorRequest newMirror,
216 Author author, User user, boolean update) {
217 final MetaRepository metaRepo = metaRepo(projectName);
218 return metaRepo.createMirrorPushCommand(repoName, newMirror, author, zoneConfig, update).thenCompose(
219 command -> {
220 return executor().execute(command).thenApply(result -> {
221 metaRepo.mirror(repoName, newMirror.id(), result.revision())
222 .handle((mirror, cause) -> {
223 if (cause != null) {
224
225 logger.warn("Failed to get the mirror: {}", newMirror.id(), cause);
226 return null;
227 }
228 return notifyMirrorEvent(mirror, user, update);
229 });
230 return new PushResultDto(result.revision(), command.timestamp());
231 });
232 });
233 }
234
235 private Void notifyMirrorEvent(Mirror mirror, User user, boolean update) {
236 try {
237 final MirrorListener listener = MirrorSchedulingService.mirrorListener();
238 if (update) {
239 listener.onUpdate(mirror, user, accessController);
240 } else {
241 listener.onCreate(mirror, user, accessController);
242 }
243 } catch (Throwable ex) {
244 logger.warn("Failed to notify the mirror listener. (mirror: {})", mirror, ex);
245 }
246 return null;
247 }
248
249
250
251
252
253
254
255 @RequestTimeout(value = 5, unit = TimeUnit.MINUTES)
256 @Post("/projects/{projectName}/repos/{repoName}/mirrors/{mirrorId}/run")
257 @RequiresRepositoryRole(RepositoryRole.ADMIN)
258 public CompletableFuture<MirrorResult> runMirror(@Param String projectName,
259 Repository repository,
260 @Param String mirrorId,
261 User user) throws Exception {
262 return mirrorRunner.run(projectName, repository.name(), mirrorId, user);
263 }
264
265
266
267
268
269
270 @Get("/mirror/config")
271 public Map<String, Object> config() {
272
273 return mirrorZoneConfig;
274 }
275
276 private CompletableFuture<List<MirrorDto>> convertToMirrorDtos(
277 String projectName, CompletableFuture<List<Mirror>> future) {
278 return future.thenCompose(mirrors -> {
279 final ImmutableList<String> remoteUris = mirrors.stream().map(
280 mirror -> mirror.remoteRepoUri().toString()).collect(
281 toImmutableList());
282 return accessController.isAllowed(remoteUris).thenApply(acl -> {
283 return mirrors.stream()
284 .map(mirror -> convertToMirrorDto(projectName, mirror, acl))
285 .collect(toImmutableList());
286 });
287 });
288 }
289
290 private static MirrorDto convertToMirrorDto(String projectName, Mirror mirror, Map<String, Boolean> acl) {
291 final boolean allowed = acl.get(mirror.remoteRepoUri().toString());
292 return convertToMirrorDto(projectName, mirror, allowed);
293 }
294
295 private static MirrorDto convertToMirrorDto(String projectName, Mirror mirror, boolean allowed) {
296 final URI remoteRepoUri = mirror.remoteRepoUri();
297 final Cron schedule = mirror.schedule();
298 final String scheduleStr = schedule != null ? schedule.asString() : null;
299 return new MirrorDto(mirror.id(),
300 mirror.enabled(), projectName,
301 scheduleStr,
302 mirror.direction().name(),
303 mirror.localRepo().name(),
304 mirror.localPath(),
305 remoteRepoUri.getScheme(),
306 remoteRepoUri.getAuthority() + remoteRepoUri.getPath(),
307 mirror.remotePath(),
308 mirror.remoteBranch(),
309 mirror.gitignore(),
310 mirror.credential().name(), mirror.zone(), allowed);
311 }
312
313 private MetaRepository metaRepo(String projectName) {
314 return projectApiManager.getProject(projectName).metaRepo();
315 }
316 }