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
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ make install
# Set environment variables (optional)
export GITLAB_URL=https://your-gitlab-instance.com
export GITLAB_TOKEN=your-personal-access-token
# Optional: SSH endpoint for clone/push templates
export GITLAB_SSH_ENDPOINT=ssh://git@your-gitlab.com:ssh-port

# Create user, groups, and projects
./bin/gitlab-cli user create \
Expand All @@ -58,6 +60,7 @@ export GITLAB_TOKEN=your-personal-access-token
./bin/gitlab-cli user create \
--host https://your-gitlab.com \
--token your-token \
--ssh-endpoint ssh://git@your-gitlab.com:ssh-port \
-f config.yaml \
-o output.yaml \
-t template.yaml
Expand Down Expand Up @@ -216,6 +219,7 @@ token:
```

Output contains all created resource information:
- GitLab endpoint info: endpoint, scheme, host, port, ssh (endpoint/host/port when provided)
- User info: username, email, name, user_id, password
- Token info: value, scope, expires_at (if token is configured)
- Group info: name, path, group_id, visibility (if groups are configured)
Expand All @@ -224,6 +228,14 @@ Output contains all created resource information:
Output format:

```yaml
endpoint: https://your-gitlab.com
scheme: https
host: your-gitlab.com
port: 443
ssh:
endpoint: ssh://git@your-gitlab.com:ssh-port
host: your-gitlab.com
port: ssh-port
users:
- username: tektoncd
email: tektoncd001@test.example.com
Expand Down Expand Up @@ -299,6 +311,12 @@ toolchains:
endpoint: {{ $.Endpoint }}
host: {{ $.Host }}
scheme: {{ $.Scheme }}
{{- if $.SSH }}
ssh:
endpoint: {{ $.SSH.Endpoint }}
host: {{ $.SSH.Host }}
port: {{ $.SSH.Port }}
{{- end }}
# User information
username: {{ .Username }}
email: {{ .Email }}
Expand All @@ -325,6 +343,7 @@ toolchains:

**Template Notes:**
- `default: {{ .Username }}` - Specifies the default group, newly created projects will use this username as the namespace by default
- `.SSH` is available when `--ssh-endpoint` or `GITLAB_SSH_ENDPOINT` is provided, useful for clone/push configs

Using the template:

Expand Down
7 changes: 7 additions & 0 deletions examples/template-example.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# GitLab CLI 输出模板示例
# 支持 Go template 语法,可以访问 OutputConfig 结构中的所有数据
# 数据结构: .Endpoint (完整 URL), .Scheme (http/https), .Host (主机名), .Port (端口号) 是 GitLab 服务器配置
# Data: .SSH provides SSH endpoint, host and port when available
# 数据结构: .Users[0] 包含 Username, Email, Name, UserID, Password, Token, Groups, Projects

{{- range .Users }}
Expand All @@ -17,6 +18,12 @@ toolchains:
port: {{ $.Port }}
# scheme, http or https
scheme: {{ $.Scheme }}
{{- if $.SSH }}
ssh:
endpoint: {{ $.SSH.Endpoint }}
host: {{ $.SSH.Host }}
port: {{ $.SSH.Port }}
{{- end }}
Comment on lines +21 to +26

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

已验证,能够生成 ssh 配置

Image

# username, the user name of the login account
username: {{ .Username }}
# email
Expand Down
61 changes: 61 additions & 0 deletions internal/cli/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"fmt"
"log"
"net/url"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -71,6 +72,7 @@ func buildUserCreateCommand(cfg *config.CLIConfig) *cobra.Command {
cmd.Flags().StringVarP(&cfg.ConfigFile, "config", "f", "../test-users.yaml", "配置文件路径")
cmd.Flags().StringVar(&cfg.GitLabHost, "host", "", "GitLab 主机地址")
cmd.Flags().StringVar(&cfg.GitLabToken, "token", "", "GitLab Personal Access Token")
cmd.Flags().StringVar(&cfg.GitLabSSHEndpoint, "ssh-endpoint", "", "GitLab SSH endpoint (e.g., ssh://git@host:22)")
cmd.Flags().StringVarP(&cfg.OutputFile, "output", "o", "", "输出结果到 YAML 文件")
cmd.Flags().StringVarP(&cfg.TemplateFile, "template", "t", "", "使用模板文件格式化输出")

Expand Down Expand Up @@ -175,11 +177,25 @@ func runUserCreate(cfg *config.CLIConfig) error {
// 从 GitLabHost 解析 endpoint、scheme、host 和 port
endpoint, scheme, host, port := parseGitLabHostURL(cfg.GitLabHost)

// Parse SSH endpoint information for template rendering
sshEndpoint, sshHost, sshPort := parseGitLabSSHEndpoint(cfg.GitLabSSHEndpoint)

// sshConfig holds parsed SSH endpoint details when available
var sshConfig *types.SSHConfig
if sshEndpoint != "" {
sshConfig = &types.SSHConfig{
Endpoint: sshEndpoint,
Host: sshHost,
Port: sshPort,
}
}

output := &types.OutputConfig{
Endpoint: endpoint,
Scheme: scheme,
Host: host,
Port: port,
SSH: sshConfig,
Users: userOutputs,
}

Expand Down Expand Up @@ -495,6 +511,51 @@ func initializeClient(cfg *config.CLIConfig) (*client.GitLabClient, error) {
return gitlabClient, nil
}

// parseGitLabSSHEndpoint extracts endpoint, host, and port from the SSH URL string
func parseGitLabSSHEndpoint(rawEndpoint string) (endpoint, host string, port int) {
// trimmedEndpoint stores the SSH endpoint without trailing slash or spaces
trimmedEndpoint := strings.TrimSpace(rawEndpoint)
if trimmedEndpoint == "" {
return "", "", 0
}

// normalizedEndpoint ensures the SSH URL includes a scheme for parsing
normalizedEndpoint := strings.TrimSuffix(trimmedEndpoint, "/")
if !strings.Contains(normalizedEndpoint, "://") {
normalizedEndpoint = "ssh://" + normalizedEndpoint
}

// parsedURL holds the parsed SSH URL result
parsedURL, err := url.Parse(normalizedEndpoint)
if err != nil {
return normalizedEndpoint, "", 0
}

// hostName captures the hostname portion of the SSH URL
hostName := parsedURL.Hostname()
// portNumber defaults to standard SSH port
portNumber := 22
if parsedURL.Port() != "" {
if parsedPort, err := strconv.Atoi(parsedURL.Port()); err == nil {
portNumber = parsedPort
} else {
portNumber = 0
}
}

// endpointBuilder rebuilds the endpoint including user info when available
endpointBuilder := parsedURL.Scheme + "://"
if parsedURL.User != nil {
endpointBuilder += parsedURL.User.String() + "@"
}
endpointBuilder += hostName
if portNumber > 0 {
endpointBuilder += ":" + strconv.Itoa(portNumber)
}

return endpointBuilder, hostName, portNumber
}

// parseGitLabHostURL 从 GitLab Host URL 解析出 endpoint、scheme 和 host
func parseGitLabHostURL(gitlabHost string) (endpoint, scheme, host string, port int) {
// 默认值
Expand Down
17 changes: 11 additions & 6 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (

// CLIConfig 封装CLI配置参数
type CLIConfig struct {
ConfigFile string
GitLabHost string
GitLabToken string
OutputFile string // 输出文件路径
TemplateFile string // 模板文件路径
DaysOld int // 只删除创建日期超过指定天数的用户(cleanup 命令使用)
ConfigFile string
GitLabHost string
GitLabToken string
OutputFile string // 输出文件路径
TemplateFile string // 模板文件路径
DaysOld int // 只删除创建日期超过指定天数的用户(cleanup 命令使用)
GitLabSSHEndpoint string // GitLab SSH endpoint (e.g., ssh://git@host:22)
}

// LoadGitLabCredentials 从环境变量或命令行参数加载 GitLab 凭证
Expand All @@ -32,6 +33,10 @@ func LoadGitLabCredentials(cfg *CLIConfig) error {
return fmt.Errorf("GitLab token is required (use --token or GITLAB_TOKEN env)")
}
}
// Populate SSH endpoint from environment when flag is not provided
if cfg.GitLabSSHEndpoint == "" {
cfg.GitLabSSHEndpoint = os.Getenv("GITLAB_SSH_ENDPOINT")
}
return nil
}

Expand Down
18 changes: 13 additions & 5 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,21 @@ type ProjectSpec struct {
// 输出结果类型
// ========================================

// SSHConfig captures parsed SSH endpoint information
type SSHConfig struct {
Endpoint string `yaml:"endpoint"` // Endpoint is the normalized SSH endpoint string
Host string `yaml:"host"` // Host is the SSH hostname extracted from the endpoint
Port int `yaml:"port"` // Port is the SSH port number
}

// OutputConfig 输出配置结构
type OutputConfig struct {
Endpoint string `yaml:"endpoint"`
Scheme string `yaml:"scheme"`
Host string `yaml:"host"`
Port int `yaml:"port"`
Users []UserOutput `yaml:"users"`
Endpoint string `yaml:"endpoint"` // Endpoint is the normalized HTTP endpoint of GitLab
Scheme string `yaml:"scheme"` // Scheme is the HTTP scheme of the GitLab endpoint
Host string `yaml:"host"` // Host is the hostname of the GitLab endpoint
Port int `yaml:"port"` // Port is the HTTP port of the GitLab endpoint
SSH *SSHConfig `yaml:"ssh,omitempty"` // SSH holds parsed SSH endpoint details when available
Users []UserOutput `yaml:"users"` // Users carries all generated user outputs
}

// UserOutput 用户输出结果
Expand Down
7 changes: 7 additions & 0 deletions template-example.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# GitLab CLI 输出模板示例
# 支持 Go template 语法,可以访问 OutputConfig 结构中的所有数据
# 数据结构: .Endpoint (完整 URL), .Scheme (http/https), .Host (主机名) 是 GitLab 服务器配置
# Data: .SSH provides SSH endpoint, host and port when available
# 数据结构: .Users[0] 包含 Username, Email, Name, UserID, Token, Groups

{{- range .Users }}
Expand All @@ -15,6 +16,12 @@ toolchains:
host: {{ $.Host }}
# scheme, http or https
scheme: {{ $.Scheme }}
{{- if $.SSH }}
ssh:
endpoint: {{ $.SSH.Endpoint }}
host: {{ $.SSH.Host }}
port: {{ $.SSH.Port }}
{{- end }}
# username, the user name of the login account
username: {{ .Username }}
# email
Expand Down