001package org.kuali.common.devops.jenkins.upgrade.model; 002 003import static com.google.common.base.Optional.absent; 004import static com.google.common.base.Preconditions.checkNotNull; 005import static com.google.common.collect.Iterables.filter; 006import static com.google.common.collect.Lists.newArrayList; 007import static com.google.common.collect.Ordering.natural; 008import static java.lang.String.format; 009import static org.kuali.common.core.io.Files.buildUnixFile; 010import static org.kuali.common.core.io.Files.checkIsDir; 011import static org.kuali.common.devops.jenkins.upgrade.Jenkins.BUILDS_DIRECTORY; 012import static org.kuali.common.devops.jenkins.upgrade.Jenkins.buildNumberMap; 013import static org.kuali.common.devops.jenkins.upgrade.Jenkins.isJenkinsBuildDirectory; 014import static org.kuali.common.devops.jenkins.upgrade.Jenkins.listBuildDirContents; 015import static org.kuali.common.util.base.Precondition.checkNotBlank; 016import static org.kuali.common.util.base.Precondition.checkNotNull; 017import static org.kuali.common.util.log.Loggers.newLogger; 018 019import java.io.IOException; 020import java.nio.file.Path; 021import java.nio.file.Paths; 022import java.util.List; 023import java.util.Map; 024 025import org.kuali.common.core.io.UnixFile; 026import org.slf4j.Logger; 027 028import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 029import com.google.common.base.Optional; 030import com.google.common.base.Predicate; 031import com.google.common.collect.ImmutableList; 032 033@JsonDeserialize(builder = JobSummary.Builder.class) 034public final class JobSummary { 035 036 private static final Logger logger = newLogger(); 037 038 private final String name; 039 private final UnixFile basedir; 040 private final ImmutableList<BuildSummary> builds; 041 private final Optional<BuildSummary> lastFailedBuild; 042 private final Optional<BuildSummary> lastStableBuild; 043 private final Optional<BuildSummary> lastSuccessfulBuild; 044 private final Optional<BuildSummary> lastUnstableBuild; 045 private final Optional<BuildSummary> lastUnsuccessfulBuild; 046 047 public static JobSummary build(UnixFile basedir, String timeZoneId, long startedBefore) throws IOException { 048 checkIsDir(basedir); 049 List<UnixFile> files = listBuildDirContents(basedir); 050 Map<String, Integer> buildNumbers = buildNumberMap(files); 051 List<BuildSummary> builds = getBuilds(files, buildNumbers, timeZoneId, startedBefore); 052 String name = basedir.getFileName(); 053 Builder builder = builder().withBasedir(basedir).withBuilds(builds).withName(name); 054 builder.withLastFailedBuild(getNamedBuild(basedir, "lastFailedBuild", builds)); 055 builder.withLastStableBuild(getNamedBuild(basedir, "lastStableBuild", builds)); 056 builder.withLastSuccessfulBuild(getNamedBuild(basedir, "lastSuccessfulBuild", builds)); 057 builder.withLastUnstableBuild(getNamedBuild(basedir, "lastUnstableBuild", builds)); 058 builder.withLastUnsuccessfulBuild(getNamedBuild(basedir, "lastUnsuccessfulBuild", builds)); 059 return builder.build(); 060 } 061 062 private static Optional<BuildSummary> getNamedBuild(UnixFile jobDir, String link, List<BuildSummary> builds) { 063 try { 064 Path path = Paths.get(jobDir.getPath() + "/" + BUILDS_DIRECTORY + "/" + link); 065 UnixFile file = buildUnixFile(path); 066 if (!file.getAttributes().isSymbolicLink()) { 067 return absent(); 068 } 069 Path realPath = path.toRealPath(); 070 UnixFile realFile = buildUnixFile(realPath); 071 for (BuildSummary build : builds) { 072 if (realFile.equals(build.getBasedir())) { 073 return Optional.of(build); 074 } 075 } 076 return absent(); 077 } catch (IOException e) { 078 return absent(); 079 } 080 } 081 082 private static List<BuildSummary> getBuilds(List<UnixFile> files, Map<String, Integer> buildNumbers, String timeZoneId, long startedBefore) throws IOException { 083 // Setup a predicate that filters out everything except timestamp'd build directories 084 Predicate<UnixFile> buildsOnly = isJenkinsBuildDirectory(); 085 086 // Convert Path objects into UnixFile objects and sort them 087 List<UnixFile> list = natural().sortedCopy(filter(files, buildsOnly)); 088 089 // Convert each build directory into a BuildSummary object 090 List<BuildSummary> builds = newArrayList(); 091 for (UnixFile element : list) { 092 try { 093 int buildNumber = checkNotNull(buildNumbers.get(element.getPath()), "no build number for [%s]", element.getPath()); 094 BuildSummary build = BuildSummary.build(element, buildNumber, timeZoneId); 095 if (build.getStartTime() > startedBefore) { 096 continue; 097 } 098 if (!build.getFiles().isEmpty()) { 099 builds.add(build); 100 } 101 } catch (IOException e) { 102 // We don't have exclusive access to the file system 103 // Jenkins may delete a build while we are working 104 logger.info(format("error -> [%s]", e)); 105 } 106 } 107 return builds; 108 } 109 110 public static Builder builder() { 111 return new Builder(); 112 } 113 114 private JobSummary(Builder builder) { 115 this.name = builder.name; 116 this.basedir = builder.basedir; 117 this.builds = ImmutableList.copyOf(builder.builds); 118 this.lastFailedBuild = builder.lastFailedBuild; 119 this.lastStableBuild = builder.lastStableBuild; 120 this.lastSuccessfulBuild = builder.lastSuccessfulBuild; 121 this.lastUnstableBuild = builder.lastUnstableBuild; 122 this.lastUnsuccessfulBuild = builder.lastUnsuccessfulBuild; 123 } 124 125 public static class Builder implements org.apache.commons.lang3.builder.Builder<JobSummary> { 126 127 private String name; 128 private UnixFile basedir; 129 private List<BuildSummary> builds = newArrayList(); 130 private Optional<BuildSummary> lastFailedBuild = absent(); 131 private Optional<BuildSummary> lastStableBuild = absent(); 132 private Optional<BuildSummary> lastSuccessfulBuild = absent(); 133 private Optional<BuildSummary> lastUnstableBuild = absent(); 134 private Optional<BuildSummary> lastUnsuccessfulBuild = absent(); 135 136 public Builder withLastUnsuccessfulBuild(Optional<BuildSummary> lastUnsuccessfulBuild) { 137 this.lastUnsuccessfulBuild = lastUnsuccessfulBuild; 138 return this; 139 } 140 141 public Builder withLastUnstableBuild(Optional<BuildSummary> lastUnstableBuild) { 142 this.lastUnstableBuild = lastUnstableBuild; 143 return this; 144 } 145 146 public Builder withLastSuccessfulBuild(Optional<BuildSummary> lastSuccessfulBuild) { 147 this.lastSuccessfulBuild = lastSuccessfulBuild; 148 return this; 149 } 150 151 public Builder withLastStableBuild(Optional<BuildSummary> lastStableBuild) { 152 this.lastStableBuild = lastStableBuild; 153 return this; 154 } 155 156 public Builder withLastFailedBuild(Optional<BuildSummary> lastFailedBuild) { 157 this.lastFailedBuild = lastFailedBuild; 158 return this; 159 } 160 161 public Builder withBasedir(UnixFile basedir) { 162 this.basedir = basedir; 163 return this; 164 } 165 166 public Builder withName(String name) { 167 this.name = name; 168 return this; 169 } 170 171 public Builder withBuilds(List<BuildSummary> builds) { 172 this.builds = builds; 173 return this; 174 } 175 176 @Override 177 public JobSummary build() { 178 return validate(new JobSummary(this)); 179 } 180 181 private static JobSummary validate(JobSummary instance) { 182 checkNotBlank(instance.name, "name"); 183 checkNotNull(instance.basedir, "basedir"); 184 checkNotNull(instance.builds, "builds"); 185 return instance; 186 } 187 } 188 189 public List<BuildSummary> getBuilds() { 190 return builds; 191 } 192 193 public String getName() { 194 return name; 195 } 196 197 public UnixFile getBasedir() { 198 return basedir; 199 } 200 201 public Optional<BuildSummary> getLastFailedBuild() { 202 return lastFailedBuild; 203 } 204 205 public Optional<BuildSummary> getLastStableBuild() { 206 return lastStableBuild; 207 } 208 209 public Optional<BuildSummary> getLastSuccessfulBuild() { 210 return lastSuccessfulBuild; 211 } 212 213 public Optional<BuildSummary> getLastUnstableBuild() { 214 return lastUnstableBuild; 215 } 216 217 public Optional<BuildSummary> getLastUnsuccessfulBuild() { 218 return lastUnsuccessfulBuild; 219 } 220 221}