All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.pulsar.shell.ConfigShell Maven / Gradle / Ivy

There is a newer version: 4.0.0-SNAPSHOT.ursa
Show newest version
/*
 * 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.pulsar.shell;

import static org.apache.pulsar.shell.config.ConfigStore.DEFAULT_CONFIG;
import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.pulsar.shell.config.ConfigStore;

/**
 * Shell commands to manage shell configurations.
 */
@Parameters(commandDescription = "Manage Pulsar shell configurations.")
public class ConfigShell implements ShellCommandsProvider {

    private static final String LOCAL_FILES_BASE_DIR = System.getProperty("pulsar.shell.working.dir");

    static File resolveLocalFile(String input) {
        return resolveLocalFile(input, LOCAL_FILES_BASE_DIR);
    }

    static File resolveLocalFile(String input, String baseDir) {
        final File file = new File(input);
        if (!file.isAbsolute() && baseDir != null) {
            return new File(baseDir, input);
        }
        return file;
    }


    @Getter
    @Parameters
    public static class Params {

        @Parameter(names = {"-h", "--help"}, help = true, description = "Show this help.")
        boolean help;
    }

    private interface RunnableWithResult {
        boolean run() throws Exception;
    }

    private JCommander jcommander;
    private Params params;
    private final PulsarShell pulsarShell;
    private final Map commands = new HashMap<>();
    private final ConfigStore configStore;
    private final ObjectMapper writer = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);
    @Getter
    private String currentConfig;

    public ConfigShell(PulsarShell pulsarShell, String currentConfig) {
        this.configStore = pulsarShell.getConfigStore();
        this.pulsarShell = pulsarShell;
        this.currentConfig = currentConfig;
    }

    @Override
    public String getName() {
        return "config";
    }

    @Override
    public String getServiceUrl() {
        return null;
    }

    @Override
    public String getAdminUrl() {
        return null;
    }

    @Override
    public void setupState(Properties properties) {

        this.params = new Params();
        this.jcommander = new JCommander();
        jcommander.addObject(params);

        commands.put("list", new CmdConfigList());
        commands.put("create", new CmdConfigCreate());
        commands.put("clone", new CmdConfigClone());
        commands.put("update", new CmdConfigUpdate());
        commands.put("delete", new CmdConfigDelete());
        commands.put("use", new CmdConfigUse());
        commands.put("view", new CmdConfigView());
        commands.put("set-property", new CmdConfigSetProperty());
        commands.put("get-property", new CmdConfigGetProperty());
        commands.forEach((k, v) -> jcommander.addCommand(k, v));
    }

    @Override
    public void cleanupState(Properties properties) {
        setupState(properties);
    }

    @Override
    public JCommander getJCommander() {
        return jcommander;
    }

    @Override
    public boolean runCommand(String[] args) throws Exception {
        try {
            jcommander.parse(args);

            if (params.help) {
                jcommander.usage();
                return true;
            }

            String chosenCommand = jcommander.getParsedCommand();
            final RunnableWithResult command = commands.get(chosenCommand);
            if (command == null) {
                jcommander.usage();
                return false;
            }
            return command.run();
        } catch (Throwable e) {
            jcommander.getConsole().println(e.getMessage());
            String chosenCommand = jcommander.getParsedCommand();
            if (e instanceof ParameterException) {
                try {
                    jcommander.getUsageFormatter().usage(chosenCommand);
                } catch (ParameterException noCmd) {
                    e.printStackTrace();
                }
            } else {
                e.printStackTrace();
            }
            return false;
        }
    }

    @Parameters(commandDescription = "List configurations")
    private class CmdConfigList implements RunnableWithResult {

        @Override
        @SneakyThrows
        public boolean run() {
            print(configStore
                    .listConfigs()
                    .stream()
                    .map(e -> formatEntry(e))
                    .collect(Collectors.toList())
            );
            return true;
        }

        private String formatEntry(ConfigStore.ConfigEntry entry) {
            final String name = entry.getName();
            if (name.equals(currentConfig)) {
                return name + " (*)";
            }
            return name;
        }
    }

    @Parameters(commandDescription = "Use the configuration for next commands")
    private class CmdConfigUse implements RunnableWithResult {
        @Parameter(description = "Name of the config", required = true)
        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.CONFIGS)
        private String name;

        @Override
        @SneakyThrows
        public boolean run() {
            final ConfigStore.ConfigEntry config = configStore.getConfig(name);
            if (config == null) {
                print("Config " + name + " not found");
                return false;
            }
            final String value = config.getValue();
            currentConfig = name;
            final Properties properties = new Properties();
            properties.load(new StringReader(value));
            pulsarShell.reload(properties);
            configStore.setLastUsed(name);
            return true;
        }
    }

    @Parameters(commandDescription = "View configuration")
    private class CmdConfigView implements RunnableWithResult {
        @Parameter(description = "Name of the config", required = true)
        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.CONFIGS)
        private String name;

        @Override
        @SneakyThrows
        public boolean run() {
            final ConfigStore.ConfigEntry config = configStore.getConfig(this.name);
            if (config == null) {
                print("Config " + name + " not found");
                return false;
            }
            print(config.getValue());
            return true;
        }
    }

    @Parameters(commandDescription = "Delete a configuration")
    private class CmdConfigDelete implements RunnableWithResult {
        @Parameter(description = "Name of the config", required = true)
        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.CONFIGS)
        private String name;

        @Override
        @SneakyThrows
        public boolean run() {
            if (DEFAULT_CONFIG.equals(name)) {
                print("'" + name + "' can't be deleted.");
                return false;
            }
            if (currentConfig != null && currentConfig.equals(name)) {
                print("'" + name + "' is currently used and it can't be deleted.");
                return false;
            }
            configStore.deleteConfig(name);
            return true;
        }
    }

    @Parameters(commandDescription = "Create a new configuration.")
    private class CmdConfigCreate extends CmdConfigPut {

        @Parameter(description = "Configuration name", required = true)
        protected String name;

        @Override
        @SneakyThrows
        boolean verifyCondition() {
            final boolean exists = configStore.getConfig(name) != null;
            if (exists) {
                print("Config '" + name + "' already exists.");
                return false;
            }
            return true;
        }

        @Override
        String name() {
            return name;
        }
    }

    @Parameters(commandDescription = "Update an existing configuration.")
    private class CmdConfigUpdate extends CmdConfigPut {

        @Parameter(description = "Configuration name", required = true)
        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.CONFIGS)
        protected String name;

        @Override
        @SneakyThrows
        boolean verifyCondition() {
            if (DEFAULT_CONFIG.equals(name)) {
                print("'" + name + "' can't be updated.");
                return false;
            }
            final boolean exists = configStore.getConfig(name) != null;
            if (!exists) {
                print("Config '" + name + "' does not exist.");
                return false;
            }
            return true;
        }

        @Override
        String name() {
            return name;
        }
    }

    private abstract class CmdConfigPut implements RunnableWithResult {

        @Parameter(names = {"--url"}, description = "URL of the config")
        protected String url;

        @Parameter(names = {"--file"}, description = "File path of the config")
        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.FILES)
        protected String file;

        @Parameter(names = {"--value"}, description = "Inline value of the config")
        protected String inlineValue;

        @Override
        @SneakyThrows
        public boolean run() {
            if (!verifyCondition()) {
                return false;
            }
            final String name = name();
            final String value;
            if (inlineValue != null) {
                if (inlineValue.startsWith("base64:")) {
                    final byte[] bytes = Base64.getDecoder().decode(inlineValue.substring("base64:".length()));
                    value = new String(bytes, StandardCharsets.UTF_8);
                } else {
                    value = inlineValue;
                }
            } else if (file != null) {
                final File f = resolveLocalFile(file);
                if (!f.exists()) {
                    print("File " + f.getAbsolutePath() + " not found.");
                    return false;
                }
                value = new String(Files.readAllBytes(f.toPath()), StandardCharsets.UTF_8);
            } else if (url != null) {
                final ByteArrayOutputStream bout = new ByteArrayOutputStream();
                try {
                    try (InputStream in = URI.create(url).toURL().openStream()) {
                        IOUtils.copy(in, bout);
                    }
                } catch (IOException | IllegalArgumentException e) {
                    print("Failed to download configuration: " + e.getMessage());
                    return false;
                }
                value = new String(bout.toByteArray(), StandardCharsets.UTF_8);
            } else {
                print("At least one between --file, --url or --value is required.");
                return false;
            }

            final ConfigStore.ConfigEntry entry = new ConfigStore.ConfigEntry(name, value);
            configStore.putConfig(entry);
            reloadIfCurrent(entry);
            return true;
        }

        abstract String name();

        abstract boolean verifyCondition();
    }


    private class CmdConfigClone implements RunnableWithResult {

        @Parameter(description = "Configuration to clone", required = true)
        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.CONFIGS)
        protected String cloneFrom;

        @Parameter(names = {"--name"}, description = "Name of the new config", required = true)
        protected String newName;

        @Override
        @SneakyThrows
        public boolean run() {
            if (DEFAULT_CONFIG.equals(newName) || configStore.getConfig(newName) != null) {
                print("'" + newName + "' already exists.");
                return false;
            }
            final ConfigStore.ConfigEntry config = configStore.getConfig(cloneFrom);
            if (config == null) {
                print("Config '" + config + "' does not exist.");
                return false;
            }

            final ConfigStore.ConfigEntry entry = new ConfigStore.ConfigEntry(newName, config.getValue());
            configStore.putConfig(entry);
            reloadIfCurrent(entry);
            return true;
        }
    }

    private void reloadIfCurrent(ConfigStore.ConfigEntry entry) throws Exception {
        if (currentConfig.equals(entry.getName())) {
            final Properties properties = new Properties();
            properties.load(new StringReader(entry.getValue()));
            pulsarShell.reload(properties);
        }
    }


    @Parameters(commandDescription = "Set a configuration property by name")
    private class CmdConfigSetProperty implements RunnableWithResult {

        @Parameter(description = "Name of the config", required = true)
        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.CONFIGS)
        private String name;

        @Parameter(names = {"-p", "--property"}, required = true, description = "Name of the property to update")
        protected String propertyName;

        @Parameter(names = {"-v", "--value"}, description = "New value for the property")
        protected String propertyValue;

        @Override
        @SneakyThrows
        public boolean run() {
            if (StringUtils.isBlank(propertyName)) {
                print("-p parameter is required");
                return false;
            }

            if (propertyValue == null) {
                print("-v parameter is required. You can pass an empty value to empty the property. (-v= )");
                return false;
            }


            final ConfigStore.ConfigEntry config = configStore.getConfig(this.name);
            if (config == null) {
                print("Config " + name + " not found");
                return false;
            }
            ConfigStore.setProperty(config, propertyName, propertyValue);
            print("Property " + propertyName + " set for config " + name);
            configStore.putConfig(config);
            reloadIfCurrent(config);
            return true;
        }
    }

    @Parameters(commandDescription = "Get a configuration property by name")
    private class CmdConfigGetProperty implements RunnableWithResult {

        @Parameter(description = "Name of the config", required = true)
        @JCommanderCompleter.ParameterCompleter(type = JCommanderCompleter.ParameterCompleter.Type.CONFIGS)
        private String name;

        @Parameter(names = {"-p", "--property"}, required = true, description = "Name of the property")
        protected String propertyName;

        @Override
        @SneakyThrows
        public boolean run() {
            if (StringUtils.isBlank(propertyName)) {
                print("-p parameter is required");
                return false;
            }

            final ConfigStore.ConfigEntry config = configStore.getConfig(this.name);
            if (config == null) {
                print("Config " + name + " not found");
                return false;
            }
            final String value = ConfigStore.getProperty(config, propertyName);
            if (!StringUtils.isBlank(value)) {
                print(value);
            }
            return true;
        }
    }



     void print(List items) {
        for (T item : items) {
            print(item);
        }
    }

     void print(T item) {
        try {
            if (item instanceof String) {
                jcommander.getConsole().println((String) item);
            } else {
                jcommander.getConsole().println(writer.writeValueAsString(item));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy