You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2019/04/24 10:48:23 UTC

[sling-org-apache-sling-committer-cli] 13/42: SLING-8311 - Investigate creating a Sling CLI tool for development task automation

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 <ra...@apache.org>
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\" <de...@sling.apache.org>\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) {