Skip to content
Merged
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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,42 @@ userRepositoryPermissions:

Refer to `config/example.yaml` for more configuration options.

### NameMode (Optional)

`nameMode` controls how resource identifiers are generated before resources are created:

- `name`: keep configured values unchanged (default behavior)
- `suffix`: append an auto-generated timestamp suffix (format: `YYYYMMDDHHMMSS`)

You can set `nameMode` globally and override it per resource:

```yaml
nameMode: "suffix"

users:
- id: "repo-admin"
emailAddress: "repo-admin@example.com"
roles:
- "repository-manager"

repositories:
- nameMode: "name" # override global suffix mode for this repository
name: "maven-releases"
format: "maven2"
type: "hosted"
online: true
storage:
blobStoreName: "default"
strictContentTypeValidation: true
```

To reuse the same resolved names in later cleanup workflows, persist the resolved config:

```bash
nexus-cli create -c my-config.yaml --resolved-config resolved-config.yaml
nexus-cli delete -c resolved-config.yaml
```

## Apply Configuration

```bash
Expand Down
34 changes: 28 additions & 6 deletions cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,16 @@ import (
)

var (
outputFormat string
// outputFormat stores the formatter output mode selected by CLI flag.
outputFormat string
// outputTemplate stores the template file path used by template output mode.
outputTemplate string
outputFile string
quiet bool
// outputFile stores the destination file path for command output.
outputFile string
// resolvedConfigPath stores the destination file path of resolved config.
resolvedConfigPath string
// quiet controls whether info logs are suppressed.
quiet bool
)

var createCmd = &cobra.Command{
Expand Down Expand Up @@ -62,6 +68,7 @@ func init() {
createCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "Output format (text|json|yaml|template|table)")
createCmd.Flags().StringVar(&outputTemplate, "output-template", "", "Template file to format resource output")
createCmd.Flags().StringVar(&outputFile, "output-file", "", "File to write resource output (stdout if not specified)")
createCmd.Flags().StringVar(&resolvedConfigPath, "resolved-config", "", "File to write resolved config used by create/delete after nameMode processing")
createCmd.Flags().BoolVarP(&quiet, "quiet", "q", false, "Quiet mode - only show errors")
}

Expand Down Expand Up @@ -115,8 +122,23 @@ func runCreate(_ *cobra.Command, _ []string) error {

formatter.Info(fmt.Sprintf("Loaded configuration from %s", cfgFile))

// Resolve nameMode before applying resources.
resolvedCfg, suffix, err := cfg.ResolveNames()
if err != nil {
return fmt.Errorf("failed to resolve config names: %w", err)
}
if suffix != "" {
formatter.Info(fmt.Sprintf("Resolved nameMode=suffix with generated suffix: %s", suffix))
}
if resolvedConfigPath != "" {
if err := config.Save(resolvedConfigPath, resolvedCfg); err != nil {
return fmt.Errorf("failed to save resolved config: %w", err)
}
formatter.Info(fmt.Sprintf("Resolved config written to %s", resolvedConfigPath))
}

// 创建服务并执行
svc := service.NewApplyService(client, cfg, formatter)
svc := service.NewApplyService(client, resolvedCfg, formatter)
result, err := svc.Apply()
if err != nil {
return fmt.Errorf("failed to create resources: %w", err)
Expand Down Expand Up @@ -146,9 +168,9 @@ func runCreate(_ *cobra.Command, _ []string) error {
if outputTemplate != "" || outputFile != "" {
// 如果没有指定模板,使用默认的 YAML 格式输出(与配置文件格式一致)
if outputTemplate == "" {
return outputResourcesDefault(client, cfg, outputFile)
return outputResourcesDefault(client, resolvedCfg, outputFile)
}
return outputResources(client, cfg, outputTemplate, outputFile)
return outputResources(client, resolvedCfg, outputTemplate, outputFile)
}

return nil
Expand Down
21 changes: 21 additions & 0 deletions pkg/config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package config
import (
"fmt"
"os"
"path/filepath"

"gopkg.in/yaml.v3"
)
Expand All @@ -23,6 +24,26 @@ func Load(filepath string) (*Config, error) {
return &config, nil
}

// Save writes configuration to file in YAML format.
func Save(path string, cfg *Config) error {
data, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}

dir := filepath.Dir(path)
if dir != "" && dir != "." {
if err := os.MkdirAll(dir, 0o755); err != nil {
return fmt.Errorf("failed to create config directory: %w", err)
}
}

if err := os.WriteFile(path, data, 0o644); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security/secrets ERROR: resolved-config writes the full resolved YAML, which includes sensitive fields such as user passwords and proxy credentials. Writing with mode 0644 makes it world-readable on multi-user systems. Please write with 0600 (and document that the file contains secrets) to avoid credential exposure.

return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}

// GetNexusCredentials 从环境变量获取 Nexus 认证信息
func GetNexusCredentials() (url, username, password string, err error) {
url = os.Getenv("NEXUS_URL")
Expand Down
Loading
Loading