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}