Skip to content

Commit 786909d

Browse files
authored
feature(cmx): add ability to set custom username (#547)
* feature(cmx): add ability to set custom username Signed-off-by: Kyle Squizzato <kyle@replicated.com>
1 parent 43335c6 commit 786909d

File tree

2 files changed

+83
-17
lines changed

2 files changed

+83
-17
lines changed

cli/cmd/vm_endpoints.go

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,10 @@ Note: %s endpoints can only be retrieved from VMs in the "running" state.`, prot
6060
replicated vm %s-endpoint aaaaa11
6161
6262
# Get %s endpoint for a specific VM by name
63-
replicated vm %s-endpoint my-test-vm`, protocol, endpointType, protocol, endpointType)
63+
replicated vm %s-endpoint my-test-vm
64+
65+
# Get %s endpoint with a custom username
66+
replicated vm %s-endpoint my-test-vm --username custom-user`, protocol, endpointType, protocol, endpointType, protocol, endpointType)
6467

6568
cmd := &cobra.Command{
6669
Use: cmdUse,
@@ -73,6 +76,8 @@ replicated vm %s-endpoint my-test-vm`, protocol, endpointType, protocol, endpoin
7376
Annotations: map[string]string{"endpointType": endpointType},
7477
}
7578

79+
cmd.Flags().String("username", "", fmt.Sprintf("Custom username to use in %s endpoint instead of the GitHub username set in Vendor Portal", protocol))
80+
7681
parent.AddCommand(cmd)
7782

7883
return cmd
@@ -88,20 +93,24 @@ func (r *runners) VMEndpoint(cmd *cobra.Command, args []string) error {
8893
}
8994
}
9095

96+
username, err := cmd.Flags().GetString("username")
97+
if err != nil {
98+
return errors.Wrap(err, "get username")
99+
}
100+
91101
vmID, err := r.getVMIDFromArg(args[0])
92102
if err != nil {
93103
return err
94104
}
95105

96-
return r.getVMEndpoint(vmID, endpointType)
106+
return r.getVMEndpoint(vmID, endpointType, username)
97107
}
98108

99109
// getVMEndpoint retrieves and formats VM endpoint with the specified protocol
100110
// endpointType should be either "ssh" or "scp"
101-
func (r *runners) getVMEndpoint(vmID, endpointType string) error {
111+
func (r *runners) getVMEndpoint(vmID, endpointType, username string) error {
102112
var err error
103113
var vm *VM
104-
var githubUsername string
105114

106115
// Validate endpoint type
107116
if err := validateEndpointType(endpointType); err != nil {
@@ -128,19 +137,25 @@ func (r *runners) getVMEndpoint(vmID, endpointType string) error {
128137
return errors.Errorf("VM %s does not have %s endpoint configured", vm.ID, endpointType)
129138
}
130139

131-
githubUsername, err = r.kotsAPI.GetGitHubUsername()
132-
if err != nil {
133-
return errors.Wrap(err, "get github username")
134-
}
140+
// Use provided username or fetch from GitHub
141+
if username == "" {
142+
githubUsername, err := r.kotsAPI.GetGitHubUsername()
143+
if err != nil {
144+
return errors.Wrap(errors.New(`failed to obtain GitHub username from Vendor Portal
145+
Alternatively, you can use the --username flag to specify a custom username for the endpoint`), "get github username")
146+
}
147+
148+
if githubUsername == "" {
149+
return errors.Errorf(`no GitHub account associated with Vendor Portal user
150+
Visit the Account Settings page in Vendor Portal to link your account
151+
Alternatively, you can use the --username flag to specify a custom username for the endpoint`)
152+
}
135153

136-
// Format the endpoint with username if available
137-
if githubUsername == "" {
138-
return errors.Errorf(`no github account associated with vendor portal user
139-
Visit the Account Settings page in Vendor Portal to link your account`)
154+
username = githubUsername
140155
}
141156

142157
// Format the endpoint URL with the appropriate protocol
143-
fmt.Printf("%s://%s@%s:%d\n", endpointType, githubUsername, vm.DirectEndpoint, vm.DirectPort)
158+
fmt.Printf("%s://%s@%s:%d\n", endpointType, username, vm.DirectEndpoint, vm.DirectPort)
144159

145160
return nil
146161
}

cli/cmd/vm_endpoints_test.go

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,92 +23,138 @@ func TestGetVMEndpoint(t *testing.T) {
2323
name string // Test case name
2424
vmID string // Virtual machine ID to test
2525
endpointType string // Endpoint type (ssh, scp, or invalid)
26+
username string // Custom username (empty to use GitHub username)
2627
mockVM *types.VM // Mock VM response from API
2728
mockGithubUsername string // Mock GitHub username response
29+
githubAPIError bool // Should GitHub username API return an error
2830
expectedOutput string // Expected output to stdout
2931
expectedError string // Expected error string (empty for no error)
3032
}{
3133
{
3234
name: "Success - Get SSH endpoint for running VM",
3335
vmID: "vm-123",
3436
endpointType: "ssh",
37+
username: "",
3538
mockVM: &types.VM{
3639
ID: "vm-123",
3740
DirectSSHEndpoint: "test-vm.example.com",
3841
DirectSSHPort: 22,
3942
Status: types.VMStatusRunning,
4043
},
4144
mockGithubUsername: "testuser",
45+
githubAPIError: false,
4246
expectedOutput: "ssh://testuser@test-vm.example.com:22\n",
4347
expectedError: "",
4448
},
4549
{
4650
name: "Success - Get SCP endpoint for running VM",
4751
vmID: "vm-456",
4852
endpointType: "scp",
53+
username: "",
4954
mockVM: &types.VM{
5055
ID: "vm-456",
5156
DirectSSHEndpoint: "test-vm.example.com",
5257
DirectSSHPort: 22,
5358
Status: types.VMStatusRunning,
5459
},
5560
mockGithubUsername: "testuser",
61+
githubAPIError: false,
5662
expectedOutput: "scp://testuser@test-vm.example.com:22\n",
5763
expectedError: "",
5864
},
65+
{
66+
name: "Success - Custom username provided",
67+
vmID: "vm-123",
68+
endpointType: "ssh",
69+
username: "customuser",
70+
mockVM: &types.VM{
71+
ID: "vm-123",
72+
DirectSSHEndpoint: "test-vm.example.com",
73+
DirectSSHPort: 22,
74+
Status: types.VMStatusRunning,
75+
},
76+
mockGithubUsername: "testuser", // Should be ignored
77+
githubAPIError: false, // Should be ignored
78+
expectedOutput: "ssh://customuser@test-vm.example.com:22\n",
79+
expectedError: "",
80+
},
5981
{
6082
name: "Error - Missing SSH endpoint configuration",
6183
vmID: "vm-789",
6284
endpointType: "ssh",
85+
username: "",
6386
mockVM: &types.VM{
6487
ID: "vm-789",
6588
DirectSSHEndpoint: "",
6689
DirectSSHPort: 0,
6790
Status: types.VMStatusRunning,
6891
},
6992
mockGithubUsername: "testuser",
93+
githubAPIError: false,
7094
expectedOutput: "",
7195
expectedError: "VM vm-789 does not have ssh endpoint configured",
7296
},
7397
{
7498
name: "Error - Missing GitHub username",
7599
vmID: "vm-123",
76100
endpointType: "ssh",
101+
username: "",
77102
mockVM: &types.VM{
78103
ID: "vm-123",
79104
DirectSSHEndpoint: "test-vm.example.com",
80105
DirectSSHPort: 22,
81106
Status: types.VMStatusRunning,
82107
},
83108
mockGithubUsername: "",
109+
githubAPIError: false,
84110
expectedOutput: "",
85-
expectedError: "no github account associated with vendor portal user",
111+
expectedError: "no GitHub account associated with Vendor Portal user",
112+
},
113+
{
114+
name: "Error - GitHub username API error",
115+
vmID: "vm-123",
116+
endpointType: "ssh",
117+
username: "",
118+
mockVM: &types.VM{
119+
ID: "vm-123",
120+
DirectSSHEndpoint: "test-vm.example.com",
121+
DirectSSHPort: 22,
122+
Status: types.VMStatusRunning,
123+
},
124+
mockGithubUsername: "", // Won't be used due to API error
125+
githubAPIError: true,
126+
expectedOutput: "",
127+
expectedError: "--username flag to specify a custom username", // Check for new error message
86128
},
87129
{
88130
name: "Error - Invalid endpoint type",
89131
vmID: "vm-123",
90132
endpointType: "invalid",
133+
username: "",
91134
mockVM: &types.VM{
92135
ID: "vm-123",
93136
DirectSSHEndpoint: "test-vm.example.com",
94137
DirectSSHPort: 22,
95138
Status: types.VMStatusRunning,
96139
},
97140
mockGithubUsername: "testuser",
141+
githubAPIError: false,
98142
expectedOutput: "",
99143
expectedError: "invalid endpoint type: invalid",
100144
},
101145
{
102146
name: "Error - VM not in running state",
103147
vmID: "vm-123",
104148
endpointType: "ssh",
149+
username: "",
105150
mockVM: &types.VM{
106151
ID: "vm-123",
107152
DirectSSHEndpoint: "test-vm.example.com",
108153
DirectSSHPort: 22,
109154
Status: types.VMStatusProvisioning,
110155
},
111156
mockGithubUsername: "testuser",
157+
githubAPIError: false,
112158
expectedOutput: "",
113159
expectedError: "VM vm-123 is not in running state (current state: provisioning). SSH is only available for running VMs",
114160
},
@@ -117,7 +163,7 @@ func TestGetVMEndpoint(t *testing.T) {
117163
for _, tc := range tests {
118164
t.Run(tc.name, func(t *testing.T) {
119165
// Create a mock server and client for API testing
120-
server, apiClient := createMockServerAndClient(tc.vmID, tc.mockVM, tc.mockGithubUsername)
166+
server, apiClient := createMockServerAndClient(tc.vmID, tc.mockVM, tc.mockGithubUsername, tc.githubAPIError)
121167
defer server.Close()
122168

123169
// Capture stdout directly
@@ -131,7 +177,7 @@ func TestGetVMEndpoint(t *testing.T) {
131177
}
132178

133179
// Execute the function under test
134-
err := runner.getVMEndpoint(tc.vmID, tc.endpointType)
180+
err := runner.getVMEndpoint(tc.vmID, tc.endpointType, tc.username)
135181

136182
// Get captured output
137183
w.Close()
@@ -153,7 +199,7 @@ func TestGetVMEndpoint(t *testing.T) {
153199
}
154200

155201
// createMockServerAndClient sets up a mock HTTP server and returns both kotsAPI.GetVM and kotsAPI.GetGitHubUsername
156-
func createMockServerAndClient(vmID string, mockVM *types.VM, mockGithubUsername string) (*httptest.Server, *kotsclient.VendorV3Client) {
202+
func createMockServerAndClient(vmID string, mockVM *types.VM, mockGithubUsername string, githubAPIError bool) (*httptest.Server, *kotsclient.VendorV3Client) {
157203
// Create a mock HTTP server to handle API requests
158204
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
159205
w.Header().Set("Content-Type", "application/json")
@@ -168,6 +214,11 @@ func createMockServerAndClient(vmID string, mockVM *types.VM, mockGithubUsername
168214

169215
// Handle GetGitHubUsername request
170216
case r.URL.Path == "/v1/user" && r.Method == "GET":
217+
if githubAPIError {
218+
w.WriteHeader(http.StatusInternalServerError)
219+
json.NewEncoder(w).Encode(map[string]string{"error": "GitHub API error"})
220+
return
221+
}
171222
response := kotsclient.GetUserResponse{
172223
GitHubUsername: mockGithubUsername,
173224
}

0 commit comments

Comments
 (0)