This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch feature/SLING-8337 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-committer-cli.git
commit 45adcd8076d9cab47101542a3fa750a30eb2e18b Author: Radu Cotescu <[email protected]> AuthorDate: Mon Mar 25 18:33:29 2019 +0100 SLING-8311 - Investigate creating a Sling CLI tool for development task automation * added support for querying Sling's project members * improved TallyVotesCommand to list binding and non-binding votes --- .../org/apache/sling/cli/impl/people/Member.java | 58 +++++++++++ .../sling/cli/impl/people/MembersFinder.java | 110 +++++++++++++++++++++ .../sling/cli/impl/release/TallyVotesCommand.java | 57 ++++++++--- 3 files changed, 213 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/apache/sling/cli/impl/people/Member.java b/src/main/java/org/apache/sling/cli/impl/people/Member.java new file mode 100644 index 0000000..7b3cb16 --- /dev/null +++ b/src/main/java/org/apache/sling/cli/impl/people/Member.java @@ -0,0 +1,58 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.cli.impl.people; + +public class Member { + + private String id; + private String name; + private boolean isPMCMember; + + Member(String id, String name, boolean isPMCMember) { + this.id = id; + this.name = name; + this.isPMCMember = isPMCMember; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public boolean isPMCMember() { + return isPMCMember; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Member)) { + return false; + } + Member other = (Member) obj; + return id.equals(other.id); + } +} diff --git a/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java new file mode 100644 index 0000000..10cba19 --- /dev/null +++ b/src/main/java/org/apache/sling/cli/impl/people/MembersFinder.java @@ -0,0 +1,110 @@ +/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ +package org.apache.sling.cli.impl.people; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.osgi.service.component.annotations.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +@Component(service = MembersFinder.class) +public class MembersFinder { + + private static final Logger LOGGER = LoggerFactory.getLogger(MembersFinder.class); + private static final String PEOPLE_ENDPOINT = "https://whimsy.apache.org/public/public_ldap_people.json"; + private static final String PROJECTS_ENDPOINT = "https://whimsy.apache.org/public/public_ldap_projects.json"; + private static final int STALENESS_IN_HOURS = 3; + private Set<Member> members = Collections.emptySet(); + private long lastCheck = 0; + + public synchronized Set<Member> findMembers() { + final Set<Member> _members = new HashSet<>(); + if (lastCheck == 0 || System.currentTimeMillis() > lastCheck + STALENESS_IN_HOURS * 3600 * 1000) { + lastCheck = System.currentTimeMillis(); + try (CloseableHttpClient client = HttpClients.createDefault()) { + JsonParser parser = new JsonParser(); + Set<String> memberIds; + Set<String> pmcMemberIds; + try (CloseableHttpResponse response = client.execute(new HttpGet(PROJECTS_ENDPOINT))) { + try (InputStream content = response.getEntity().getContent(); + InputStreamReader reader = new InputStreamReader(content)) { + if (response.getStatusLine().getStatusCode() != 200) { + throw new IOException("Status line : " + response.getStatusLine()); + } + JsonElement jsonElement = parser.parse(reader); + JsonObject json = jsonElement.getAsJsonObject(); + JsonObject sling = json.get("projects").getAsJsonObject().get("sling").getAsJsonObject(); + memberIds = new HashSet<>(); + pmcMemberIds = new HashSet<>(); + for (JsonElement member : sling.getAsJsonArray("members")) { + memberIds.add(member.getAsString()); + } + for (JsonElement pmcMember : sling.getAsJsonArray("owners")) { + pmcMemberIds.add(pmcMember.getAsString()); + } + } + } + try (CloseableHttpResponse response = client.execute(new HttpGet(PEOPLE_ENDPOINT))) { + try (InputStream content = response.getEntity().getContent(); + InputStreamReader reader = new InputStreamReader(content)) { + if (response.getStatusLine().getStatusCode() != 200) { + throw new IOException("Status line : " + response.getStatusLine()); + } + JsonElement jsonElement = parser.parse(reader); + JsonObject json = jsonElement.getAsJsonObject(); + JsonObject people = json.get("people").getAsJsonObject(); + for (String id : memberIds) { + String name = people.get(id).getAsJsonObject().get("name").getAsString(); + _members.add(new Member(id, name, pmcMemberIds.contains(id))); + } + + } + } + members = Collections.unmodifiableSet(_members); + } catch (IOException e) { + LOGGER.error("Unable to retrieve Apache Sling project members.", e); + } + } + return members; + } + + public Member getMemberById(String id) { + for (Member member : findMembers()) { + if (id.equals(member.getId())) { + return member; + } + } + return null; + } + +} diff --git a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java index 6046405..74cdd23 100644 --- a/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java +++ b/src/main/java/org/apache/sling/cli/impl/release/TallyVotesCommand.java @@ -18,6 +18,8 @@ package org.apache.sling.cli.impl.release; import java.io.IOException; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import java.util.stream.Collectors; import org.apache.sling.cli.impl.Command; @@ -26,6 +28,8 @@ import org.apache.sling.cli.impl.mail.EmailThread; import org.apache.sling.cli.impl.mail.VoteThreadFinder; import org.apache.sling.cli.impl.nexus.StagingRepository; import org.apache.sling.cli.impl.nexus.StagingRepositoryFinder; +import org.apache.sling.cli.impl.people.Member; +import org.apache.sling.cli.impl.people.MembersFinder; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; @@ -37,7 +41,10 @@ import org.slf4j.LoggerFactory; Command.PROPERTY_NAME_SUMMARY+"=Counts votes cast for a release and generates the result email" }) public class TallyVotesCommand implements Command { - + + @Reference + private MembersFinder membersFinder; + // TODO - move to file private static final String EMAIL_TEMPLATE = "To: \"Sling Developers List\" <[email protected]>\n" + @@ -45,12 +52,17 @@ public class TallyVotesCommand implements Command { "\n" + "Hi,\n" + "\n" + - "The vote has passed with the following result :\n" + + "The vote has passed with the following result:\n" + "\n" + "+1 (binding): ##BINDING_VOTERS##\n" + - "\n" + + "+1 (non-binding): ##NON_BINDING_VOTERS##\n" + + "\n" + "I will copy this release to the Sling dist directory and\n" + - "promote the artifacts to the central Maven repository.\n"; + "promote the artifacts to the central Maven repository.\n" + + "\n" + + "Regards,\n" + + "##USER_NAME##\n" + + "\n"; private final Logger logger = LoggerFactory.getLogger(getClass()); @Reference @@ -67,16 +79,37 @@ public class TallyVotesCommand implements Command { Release release = Release.fromString(repository.getDescription()); EmailThread voteThread = voteThreadFinder.findVoteThread(release.getFullName()); - // TODO - validate which voters are binding and list them separately in the email - String bindingVoters = voteThread.getEmails().stream() - .filter( e -> isPositiveVote(e) ) - .map ( e -> e.getFrom().replaceAll("<.*>", "").trim() ) - .collect(Collectors.joining(", ")); - + Set<String> bindingVoters = new HashSet<>(); + Set<String> nonBindingVoters = new HashSet<>(); + for (Email e : voteThread.getEmails()) { + if (isPositiveVote(e)) { + String sender = e.getFrom().replaceAll("<.*>", "").trim(); + for (Member m : membersFinder.findMembers()) { + if (sender.equals(m.getName())) { + if (m.isPMCMember()) { + bindingVoters.add(sender); + } else { + nonBindingVoters.add(sender); + } + } + } + } + } + String currentUserId = System.getProperty("asf.username"); + if (currentUserId == null) { + currentUserId = System.getenv("ASF_USERNAME"); + } + Member currentUser = membersFinder.getMemberById(currentUserId); String email = EMAIL_TEMPLATE .replace("##RELEASE_NAME##", release.getFullName()) - .replace("##BINDING_VOTERS##", bindingVoters); - + .replace("##BINDING_VOTERS##", String.join(", ", bindingVoters)) + .replace("##USER_NAME##", currentUser == null ? "" : currentUser.getName()); + if (nonBindingVoters.isEmpty()) { + email = email.replace("##NON_BINDING_VOTERS##", "none"); + } else { + email = email.replace("##NON_BINDING_VOTERS##", String.join(", ", nonBindingVoters)); + } + logger.info(email); } catch (IOException e) {
