001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.server.config.repository;
018
019import static org.apache.juneau.commons.utils.FileUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.io.*;
023import java.nio.file.*;
024import java.util.*;
025
026import org.apache.juneau.config.*;
027
028/**
029 * Command that fetches configuration from a Git repository for a given project and branch.
030 *
031 * <p>
032 * Clones or pulls the configured Git repo, then loads application and project-specific
033 * configuration files into a map of {@link ConfigItem} values.
034 */
035public class GetConfiguration implements Command, GetValue<Map<String,ConfigItem>> {
036
037   private static final String APPLICATION = "APPLICATION";
038   private static final String PROJECT = "PROJECT";
039   private static final String EXT = ".cfg";
040
041   private final Map<String,ConfigItem> configs = new HashMap<>();
042
043   @SuppressWarnings({
044      "java:S1845" // Field name intentionally differs only by case from static field PROJECT
045   })
046   private String project;
047   private final String branch;
048
049   /**
050    * Constructor.
051    *
052    * @param project Project name.
053    * @param branch Branch name.
054    */
055   public GetConfiguration(String project, String branch) {
056      this.branch = branch;
057      this.project = project;
058   }
059
060   /** {@inheritDoc} */
061   @Override
062   public void execute() throws Exception {
063
064      var config = Config.create().name("juneau-server-config.cfg").build();
065
066      var pathStr = config.get("GitServer/pathLocal").orElse(null);
067
068      var git = config.get("GitServer/gitRemote").orElse(null);
069
070      // Fail fast with a clear message when the operator has not configured the local checkout directory,
071      // rather than surfacing an opaque NullPointerException from new File(null) below.
072      if (pathStr == null)
073         throw new IllegalStateException("Required configuration 'GitServer/pathLocal' is not set.");
074
075      // Trusted operator-configured local git checkout directory (GitServer/pathLocal).
076      // Serves as the confinement root for the project/application config file lookups below.
077      var root = new File(pathStr);
078
079      try (var gitControl = new GitControl(pathStr, git)) {
080
081         if (root.isDirectory()) {
082            gitControl.pullFromRepo();
083         } else {
084            gitControl.cloneRepo();
085         }
086
087         gitControl.branch(branch);
088         gitControl.pullFromRepo();
089      }
090
091      var fileDefaultStr = lcr(APPLICATION).concat(EXT);
092      var fileProjectStr = this.project.concat(EXT);
093
094      // Resolve the config file names under the checkout root through the shared boundary check
095      // so a crafted project name cannot escape the repo directory (empty = file absent).
096      var fileDefault = resolveSafely(root, fileDefaultStr).orElse(null);
097      if (fileDefault != null) {
098         var lines = new String(Files.readAllBytes(fileDefault.toPath()));
099         configs.put(APPLICATION, new ConfigItem(lines));
100      }
101
102      var fileProject = resolveSafely(root, fileProjectStr).orElse(null);
103      if (fileProject != null) {
104         var linesProject = new String(Files.readAllBytes(fileProject.toPath()));
105         configs.put(PROJECT, new ConfigItem(linesProject));
106      }
107   }
108
109   /** {@inheritDoc} */
110   @Override
111   public Map<String,ConfigItem> get() {
112      return Map.copyOf(configs);
113   }
114}