Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.ashald.envfile.EnvVarsProvider;
import net.ashald.envfile.exceptions.EnvFileException;
import net.ashald.envfile.exceptions.InvalidEnvFileException;
import net.ashald.envfile.providers.sops.SopsUtils;
import org.jetbrains.annotations.NotNull;

import java.io.File;
Expand Down Expand Up @@ -34,9 +35,15 @@ public Map<String, String> getEnvVars(
)
throws EnvFileException {

val content = isExecutable
? execute(file.getAbsolutePath(), context)
: reader.read(file);
String content;
if (isExecutable) {
content = execute(file.getAbsolutePath(), context);
} else {
content = reader.read(file);
if (file.getName().toLowerCase().contains(".enc.") && SopsUtils.isSopsFile(content)) {
content = SopsUtils.decrypt(file);
}
}

return parser.parse(content);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package net.ashald.envfile.providers.sops;

import net.ashald.envfile.exceptions.InvalidEnvFileException;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class SopsUtils {

private static Supplier<String> executableSupplier = null;
public static void setExecutableSupplier(Supplier<String> supplier) {
executableSupplier = supplier;
}

public static final List<String> SOPS_KEYWORDS = List.of("sops", "lastmodified", "version");

public static boolean isSopsFile(final String content) {
return SOPS_KEYWORDS.stream().allMatch(content::contains);
}

public static String decrypt(final File file) throws InvalidEnvFileException {
String executable = executableSupplier != null ? executableSupplier.get() : "sops";
ProcessBuilder builder = new ProcessBuilder(executable, "-d", file.getAbsolutePath());
builder.redirectErrorStream(true);
try {
Process p = builder.start();
p.waitFor(10, TimeUnit.SECONDS);
String content;
try (Scanner scanner = new Scanner(p.getInputStream(), StandardCharsets.UTF_8)) {
content = scanner.useDelimiter("\\A").next();
}
if (p.exitValue() != 0) {
throw new InvalidEnvFileException("""
Failed to decrypt file %s
SOPS exit code: %d
%s""".formatted(file.getAbsolutePath(), p.exitValue(), content));
}
return content;
} catch (IOException | InterruptedException e) {
throw new InvalidEnvFileException("Failed to decrypt file " + file.getAbsolutePath(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package net.ashald.envfile.platform;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import net.ashald.envfile.providers.sops.SopsUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@State(name = "SopsSettings", storages = @Storage("envfile.sops.xml"))
public class SopsSettings implements PersistentStateComponent<SopsSettings> {

private static final boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac");
public static final String DEFAULT_SOPS_EXECUTABLE = IS_MAC ? "/opt/homebrew/bin/sops" : "sops";

public String sopsExecutablePath = DEFAULT_SOPS_EXECUTABLE;

public SopsSettings() {
SopsUtils.setExecutableSupplier(this::getSopsExecutablePath);
}

public static SopsSettings getInstance() {
return ApplicationManager.getApplication().getService(SopsSettings.class);
}

@NotNull
@Override
public SopsSettings getState() {
return this;
}

@Override
public void loadState(@NotNull SopsSettings state) {
this.sopsExecutablePath = state.sopsExecutablePath;
}

@NotNull
public String getSopsExecutablePath() {
return (sopsExecutablePath != null && !sopsExecutablePath.isBlank()) ? sopsExecutablePath : DEFAULT_SOPS_EXECUTABLE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package net.ashald.envfile.platform.ui;

import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import net.ashald.envfile.platform.SopsSettings;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.Nullable;

import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;

public class SopsSettingsConfigurable implements Configurable {

private TextFieldWithBrowseButton sopsExecutablePathField;

@Nls(capitalization = Nls.Capitalization.Title)
@Override
public String getDisplayName() {
return "EnvFile SOPS";
}

@Nullable
@Override
public JComponent createComponent() {
sopsExecutablePathField = new TextFieldWithBrowseButton();
sopsExecutablePathField.addBrowseFolderListener(
"Select SOPS Executable",
"Select the path to the SOPS executable",
null,
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()
);

JPanel panel = new JPanel(new GridBagLayout());

GridBagConstraints labelConstraints = new GridBagConstraints();
labelConstraints.gridx = 0;
labelConstraints.gridy = 0;
labelConstraints.anchor = GridBagConstraints.WEST;
labelConstraints.insets = new Insets(0, 0, 0, 8);
panel.add(new JLabel("SOPS executable path:"), labelConstraints);

GridBagConstraints fieldConstraints = new GridBagConstraints();
fieldConstraints.gridx = 1;
fieldConstraints.gridy = 0;
fieldConstraints.fill = GridBagConstraints.HORIZONTAL;
fieldConstraints.weightx = 1.0;
panel.add(sopsExecutablePathField, fieldConstraints);

GridBagConstraints fillerConstraints = new GridBagConstraints();
fillerConstraints.gridx = 0;
fillerConstraints.gridy = 1;
fillerConstraints.weighty = 1.0;
panel.add(new JPanel(), fillerConstraints);

return panel;
}

@Override
public boolean isModified() {
return !sopsExecutablePathField.getText().equals(SopsSettings.getInstance().sopsExecutablePath);
}

@Override
public void apply() {
SopsSettings.getInstance().sopsExecutablePath = sopsExecutablePathField.getText();
}

@Override
public void reset() {
sopsExecutablePathField.setText(SopsSettings.getInstance().sopsExecutablePath);
}
}
4 changes: 3 additions & 1 deletion src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<idea-plugin>
<id>net.ashald.envfile</id>
<name>EnvFile</name>
<version>5.0.1</version>
<version>5.0.2</version>
<vendor email="envfile@ashald.net">Borys Pierov</vendor>

<description><![CDATA[
Expand Down Expand Up @@ -78,6 +78,8 @@
fileNames=".env"
patterns=".env.*"/>
<applicationService serviceImplementation="net.ashald.envfile.products.idea.scala.SbtRunConfigurationExtensionManager"/>
<applicationService serviceImplementation="net.ashald.envfile.platform.SopsSettings"/>
<applicationConfigurable instance="net.ashald.envfile.platform.ui.SopsSettingsConfigurable" id="net.ashald.envfile.sops" displayName="EnvFile SOPS" parentId="tools"/>
</extensions>

<depends optional="true" config-file="envfile-sbt.xml">org.intellij.scala</depends>
Expand Down