Skip to content

Commit

Permalink
feat: use environment vars when config file is absent (#777)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrii Bodnar <[email protected]>
  • Loading branch information
katerina20 and andrii-bodnar authored May 15, 2024
1 parent 01f4837 commit 79a52f8
Show file tree
Hide file tree
Showing 10 changed files with 123 additions and 13 deletions.
24 changes: 14 additions & 10 deletions src/main/java/com/crowdin/cli/properties/BaseProperties.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,7 @@
import java.util.Map;

import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
import static com.crowdin.cli.properties.PropertiesBuilder.API_TOKEN;
import static com.crowdin.cli.properties.PropertiesBuilder.API_TOKEN_ENV;
import static com.crowdin.cli.properties.PropertiesBuilder.BASE_PATH;
import static com.crowdin.cli.properties.PropertiesBuilder.BASE_PATH_ENV;
import static com.crowdin.cli.properties.PropertiesBuilder.BASE_URL;
import static com.crowdin.cli.properties.PropertiesBuilder.BASE_URL_ENV;
import static com.crowdin.cli.properties.PropertiesBuilder.CONFIG_FILE_PATH;
import static com.crowdin.cli.properties.PropertiesBuilder.SETTINGS;
import static com.crowdin.cli.properties.PropertiesBuilder.checkBasePathExists;
import static com.crowdin.cli.properties.PropertiesBuilder.checkBasePathIsDir;
import static com.crowdin.cli.properties.PropertiesBuilder.*;

@Data
public class BaseProperties implements Properties {
Expand Down Expand Up @@ -53,6 +44,19 @@ public void populateWithValues(BaseProperties props, Map<String, Object> map) {
props.setSettings(SettingsBean.CONFIGURATOR.buildFromMap(PropertiesBuilder.getMap(map, SETTINGS)));
}

@Override
public void populateWithEnvValues(BaseProperties props) {
if (props.getApiToken() == null) {
PropertiesBuilder.setEnvIfExists(props::setApiToken, CROWDIN_PERSONAL_TOKEN);
}
if (props.getBasePath() == null) {
PropertiesBuilder.setEnvIfExists(props::setBasePath, CROWDIN_BASE_PATH);
}
if (props.getBaseUrl() == null) {
PropertiesBuilder.setEnvIfExists(props::setBaseUrl, CROWDIN_BASE_URL);
}
}

@Override
public void populateWithDefaultValues(BaseProperties props) {
if (props == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ protected void populateWithArgParams(BaseProperties props, @NonNull BaseParams p
}
}

@Override
protected void populateWithEnvValues(BaseProperties props) {
if (props == null) {
return;
}
BaseProperties.CONFIGURATOR.populateWithEnvValues(props);
}

@Override
protected void populateWithDefaultValues(BaseProperties props) {
if (props == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import static com.crowdin.cli.BaseCli.RESOURCE_BUNDLE;
import static com.crowdin.cli.properties.PropertiesBuilder.PROJECT_ID;
import static com.crowdin.cli.properties.PropertiesBuilder.PROJECT_ID_ENV;
import static com.crowdin.cli.properties.PropertiesBuilder.CROWDIN_PROJECT_ID;

@EqualsAndHashCode(callSuper = true)
@Data
Expand All @@ -34,6 +35,14 @@ public void populateWithDefaultValues(ProjectProperties props) {
// do nothing
}

@Override
public void populateWithEnvValues(ProjectProperties props) {
if (props == null) {
return;
}
PropertiesBuilder.setEnvIfExists(props::setProjectId, CROWDIN_PROJECT_ID);
}

@Override
public PropertiesBuilder.Messages checkProperties(ProjectProperties props, CheckType checkType) {
PropertiesBuilder.Messages messages = new PropertiesBuilder.Messages();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ protected void populateWithArgParams(ProjectProperties props, @NonNull ProjectPa
}
}

@Override
protected void populateWithEnvValues(ProjectProperties props) {
if (props == null) {
return;
}
BaseProperties.CONFIGURATOR.populateWithEnvValues(props);
ProjectProperties.CONFIGURATOR.populateWithEnvValues(props);
}

@Override
protected void populateWithDefaultValues(ProjectProperties props) {
if (props == null) {
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/com/crowdin/cli/properties/PropertiesBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,26 @@ public abstract class PropertiesBuilder<T extends Properties, P extends Params>

public static final String PROJECT_ID_ENV = "project_id_env";

public static final String CROWDIN_PROJECT_ID = "CROWDIN_PROJECT_ID";

public static final String API_TOKEN = "api_token";

public static final String API_TOKEN_ENV = "api_token_env";

public static final String CROWDIN_PERSONAL_TOKEN = "CROWDIN_PERSONAL_TOKEN";

public static final String BASE_PATH = "base_path";

public static final String BASE_PATH_ENV = "base_path_env";

public static final String CROWDIN_BASE_PATH = "CROWDIN_BASE_PATH";

public static final String BASE_URL = "base_url";

public static final String BASE_URL_ENV = "base_url_env";

public static final String CROWDIN_BASE_URL = "CROWDIN_BASE_URL";

public static final String PRESERVE_HIERARCHY = "preserve_hierarchy";

public static final String FILES = "files";
Expand Down Expand Up @@ -161,6 +169,7 @@ public T build() {
this.throwErrorIfNeeded(this.checkArgParams(params), RESOURCE_BUNDLE.getString("error.params_are_invalid"));
this.populateWithArgParams(props, params);
}
this.populateWithEnvValues(props);
this.populateWithDefaultValues(props);
String errorTitle = (configFileParams == null && identityFileParams == null)
? RESOURCE_BUNDLE.getString("error.configuration_is_invalid")
Expand Down Expand Up @@ -191,6 +200,8 @@ private void throwErrorIfNeeded(Messages messages, String text) {

protected abstract void populateWithArgParams(T props, @NonNull P params);

protected abstract void populateWithEnvValues(T props);

protected abstract void populateWithDefaultValues(T props);

protected abstract Messages checkProperties(T props);
Expand Down Expand Up @@ -224,6 +235,13 @@ static void setEnvOrPropertyIfExists(Consumer<String> setter, Map<String, Object
}
}

static void setEnvIfExists(Consumer<String> setter, String envKey) {
String param = getDotenv().get(envKey);
if (param != null) {
setter.accept(param);
}
}

private static Dotenv getDotenv() {
if (dotenv == null) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,19 @@ protected void populateWithArgParams(AllProperties props, @NonNull NoParams para

}

@Override
protected void populateWithEnvValues(AllProperties props) {
if (props == null) {
return;
}
BaseProperties.CONFIGURATOR.populateWithEnvValues(props.getProjectProperties());
ProjectProperties.CONFIGURATOR.populateWithEnvValues(props.getProjectProperties());

BaseProperties.CONFIGURATOR.populateWithEnvValues(props.getPropertiesWithFiles());
ProjectProperties.CONFIGURATOR.populateWithEnvValues(props.getPropertiesWithFiles());
PropertiesWithFiles.CONFIGURATOR.populateWithEnvValues(props.getPropertiesWithFiles());
}

@Override
protected void populateWithDefaultValues(AllProperties props) {
if (props == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface PropertiesConfigurator<P extends Properties> {

void populateWithDefaultValues(P props);

void populateWithEnvValues(P props);

PropertiesBuilder.Messages checkProperties(P props, CheckType checkType);

enum CheckType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public void populateWithDefaultValues(PropertiesWithFiles props) {
}
}

@Override
public void populateWithEnvValues(PropertiesWithFiles props) {
// do nothing
}

@Override
public PropertiesBuilder.Messages checkProperties(PropertiesWithFiles props, CheckType checkType) {
PropertiesBuilder.Messages messages = new PropertiesBuilder.Messages();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ protected void populateWithArgParams(@NonNull PropertiesWithFiles props, @NonNul
}
}

@Override
protected void populateWithEnvValues(PropertiesWithFiles props) {
if (props == null) {
return;
}
BaseProperties.CONFIGURATOR.populateWithEnvValues(props);
ProjectProperties.CONFIGURATOR.populateWithEnvValues(props);
PropertiesWithFiles.CONFIGURATOR.populateWithEnvValues(props);
}

@Override
protected void populateWithDefaultValues(PropertiesWithFiles props) {
if (props == null) {
Expand Down
38 changes: 35 additions & 3 deletions website/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ crowdin upload sources --config /path/to/your/config/file

### Sample configuration file

```yaml
```yml title="crowdin.yml"
"project_id": "12"
"api_token": "54e01--your-personal-token--2724a"
"base_path": "."
Expand All @@ -47,11 +47,43 @@ crowdin upload sources --config /path/to/your/config/file
| `base_url` | Crowdin API base URL. Can be omitted for crowdin.com. For Crowdin Enterprise use the `https://{organization-name}.api.crowdin.com` |

:::info

For more information how to configure Crowdin CLI, check the [Configuration File](https://developer.crowdin.com/configuration-file/) article.

:::

## Environment Variables

Crowdin CLI supports the use of environment variables for configuration. For example, you can load the API credentials from an environment variable:

```yml title="crowdin.yml"
"project_id_env": "CROWDIN_PROJECT_ID"
"api_token_env": "CROWDIN_PERSONAL_TOKEN"
"base_path_env": "CROWDIN_BASE_PATH"
"base_url_env": "CROWDIN_BASE_URL"
```
Environment variables have lower priority and will be used if any of the parameters are missing:
```yml title="crowdin.yml"
"project_id_env": "CROWDIN_PROJECT_ID" # Low priority
"api_token_env": "CROWDIN_PERSONAL_TOKEN" # Low priority
"base_path_env": "CROWDIN_BASE_PATH" # Low priority
"base_url_env": "CROWDIN_BASE_PATH" # Low priority

"project_id": "project-id" # High priority
"api_token": "personal-access-token" # High priority
"base_path": "/project-base-path" # High priority
"base_url": "https://api.crowdin.com" # High priority
```
The CLI will also **automatically** pick up the environment variables if they are set in the shell. The supported environment variables are:
| Variable Name | Description |
|--------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CROWDIN_PERSONAL_TOKEN` | Personal Access Token required for authentication |
| `CROWDIN_PROJECT_ID` | Numerical ID of the Crowdin project |
| `CROWDIN_BASE_URL` | Base URL of Crowdin server for API requests execution (`https://api.crowdin.com` for crowdin.com, `https://{organization-name}.api.crowdin.com` for Crowdin Enterprise) |
| `CROWDIN_BASE_PATH` | Path to your project directory on a local machine (default: `.`) |

## Further Reading

- [Configuration File](https://developer.crowdin.com/configuration-file/)
Expand Down

0 comments on commit 79a52f8

Please sign in to comment.