diff --git a/doc/ovhcloud_cloud_ssh-key_create.md b/doc/ovhcloud_cloud_ssh-key_create.md index 8ab1cb65..56d3aafb 100644 --- a/doc/ovhcloud_cloud_ssh-key_create.md +++ b/doc/ovhcloud_cloud_ssh-key_create.md @@ -15,7 +15,6 @@ ovhcloud cloud ssh-key create [flags] --init-file string Create a file with example parameters --name string Name for the SSH key to create --public-key string Public key for the SSH key to create - --region string Region for the SSH key to create (optional) --replace Replace parameters file if it already exists ``` diff --git a/doc/ovhcloud_cloud_ssh-key_delete.md b/doc/ovhcloud_cloud_ssh-key_delete.md index a23c1793..759d9ce3 100644 --- a/doc/ovhcloud_cloud_ssh-key_delete.md +++ b/doc/ovhcloud_cloud_ssh-key_delete.md @@ -3,7 +3,7 @@ Delete a SSH key ``` -ovhcloud cloud ssh-key delete [flags] +ovhcloud cloud ssh-key delete [flags] ``` ### Options diff --git a/doc/ovhcloud_cloud_ssh-key_get.md b/doc/ovhcloud_cloud_ssh-key_get.md index 9acf0cba..1b02114d 100644 --- a/doc/ovhcloud_cloud_ssh-key_get.md +++ b/doc/ovhcloud_cloud_ssh-key_get.md @@ -3,7 +3,7 @@ Get information about a SSH key ``` -ovhcloud cloud ssh-key get [flags] +ovhcloud cloud ssh-key get [flags] ``` ### Options diff --git a/internal/assets/api-schemas/cloud_v2.json b/internal/assets/api-schemas/cloud_v2.json index d03d70b5..88b6c78f 100644 --- a/internal/assets/api-schemas/cloud_v2.json +++ b/internal/assets/api-schemas/cloud_v2.json @@ -1157,6 +1157,52 @@ "description": "Time (e.g., 15:04:05)", "format": "time", "example": "15:04:05" + }, + "publicCloud.sshKey.SSHKeyCreation": { + "type": "object", + "description": "Payload to create a Public Cloud SSH key", + "properties": { + "name": { + "type": "string", + "description": "Unique name for the SSH key" + }, + "publicKey": { + "type": "string", + "description": "SSH public key content" + } + }, + "required": [ + "name", + "publicKey" + ] + }, + "publicCloud.sshKey.SSHKey": { + "type": "object", + "description": "A Public Cloud SSH key (stored in database, synced to OpenStack on instance creation)", + "properties": { + "createdAt": { + "type": "string", + "description": "Creation date of the SSH key", + "format": "date-time", + "readOnly": true + }, + "name": { + "type": "string", + "description": "Unique name of the SSH key", + "readOnly": true + }, + "publicKey": { + "type": "string", + "description": "SSH public key content", + "readOnly": true + }, + "updatedAt": { + "type": "string", + "description": "Last update date of the SSH key", + "format": "date-time", + "readOnly": true + } + } } }, "securitySchemes": { @@ -3192,6 +3238,331 @@ } ] } + }, + "/publicCloud/project/{projectId}/sshKey": { + "get": { + "summary": "List SSH keys", + "security": [ + { + "oAuth2AuthCode": [] + } + ], + "parameters": [ + { + "in": "header", + "name": "X-Pagination-Cursor", + "description": "Pagination cursor", + "schema": { + "type": "string" + } + }, + { + "in": "header", + "name": "X-Pagination-Size", + "description": "Pagination size", + "schema": { + "type": "integer" + } + }, + { + "in": "path", + "name": "projectId", + "description": "Project ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/publicCloud.sshKey.SSHKey" + } + } + } + } + } + }, + "tags": [ + "Compute" + ], + "x-badges": [ + { + "color": "blue", + "label": "Alpha version" + } + ], + "x-iam-actions": [ + { + "name": "publicCloudProject:apiovh:sshKey/get", + "required": true + } + ], + "x-response-identifier": "name", + "x-expanded-response": "PublicCloudSshKeySSHKey" + }, + "post": { + "summary": "Create an SSH key", + "security": [ + { + "oAuth2AuthCode": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "projectId", + "description": "Project ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/publicCloud.sshKey.SSHKeyCreation" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/publicCloud.sshKey.SSHKey" + } + } + } + }, + "409": { + "description": "Error 409 response", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Common error model", + "properties": { + "class": { + "type": "string", + "description": "Class of the error" + }, + "message": { + "type": "string", + "description": "Error message" + } + } + }, + "examples": { + "Client::Conflict::NameAlreadyInUse": { + "value": { + "class": "Client::Conflict::NameAlreadyInUse", + "message": "This name is already in use" + } + } + } + } + } + } + }, + "tags": [ + "Compute" + ], + "x-badges": [ + { + "color": "blue", + "label": "Alpha version" + } + ], + "x-iam-actions": [ + { + "name": "publicCloudProject:apiovh:sshKey/create", + "required": true + } + ] + } + }, + "/publicCloud/project/{projectId}/sshKey/{name}": { + "delete": { + "summary": "Delete an SSH key", + "security": [ + { + "oAuth2AuthCode": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "name", + "description": "Name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "projectId", + "description": "Project ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/publicCloud.sshKey.SSHKey" + } + } + } + }, + "404": { + "description": "Error 404 response", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Common error model", + "properties": { + "class": { + "type": "string", + "description": "Class of the error" + }, + "message": { + "type": "string", + "description": "Error message" + } + } + }, + "examples": { + "Client::NotFound::SSHKeyDoesNotExist": { + "value": { + "class": "Client::NotFound::SSHKeyDoesNotExist", + "message": "SSH key not found" + } + } + } + } + } + } + }, + "tags": [ + "Compute" + ], + "x-badges": [ + { + "color": "blue", + "label": "Alpha version" + } + ], + "x-iam-actions": [ + { + "name": "publicCloudProject:apiovh:sshKey/delete", + "required": true + } + ] + }, + "get": { + "summary": "Get an SSH key", + "security": [ + { + "oAuth2AuthCode": [] + } + ], + "parameters": [ + { + "in": "path", + "name": "name", + "description": "Name", + "required": true, + "schema": { + "type": "string" + } + }, + { + "in": "path", + "name": "projectId", + "description": "Project ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/publicCloud.sshKey.SSHKey" + } + } + } + }, + "404": { + "description": "Error 404 response", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Common error model", + "properties": { + "class": { + "type": "string", + "description": "Class of the error" + }, + "message": { + "type": "string", + "description": "Error message" + } + } + }, + "examples": { + "Client::NotFound::SSHKeyDoesNotExist": { + "value": { + "class": "Client::NotFound::SSHKeyDoesNotExist", + "message": "SSH key not found" + } + } + } + } + } + } + }, + "tags": [ + "Compute" + ], + "x-badges": [ + { + "color": "blue", + "label": "Alpha version" + } + ], + "x-iam-actions": [ + { + "name": "publicCloudProject:apiovh:sshKey/get", + "required": true + } + ], + "x-response-identifier": "name" + } } }, "tags": [ diff --git a/internal/cmd/cloud_ssh_key.go b/internal/cmd/cloud_ssh_key.go index 2778040a..c52e3ff2 100644 --- a/internal/cmd/cloud_ssh_key.go +++ b/internal/cmd/cloud_ssh_key.go @@ -26,7 +26,7 @@ func initCloudSSHKeyCommand(cloudCmd *cobra.Command) { sshKeyCmd.AddCommand(withFilterFlag(sshKeyListCmd)) sshKeyCmd.AddCommand(&cobra.Command{ - Use: "get ", + Use: "get ", Short: "Get information about a SSH key", Run: cloud.GetCloudSSHKey, Args: cobra.ExactArgs(1), @@ -39,14 +39,13 @@ func initCloudSSHKeyCommand(cloudCmd *cobra.Command) { } sshKeyCreateCmd.Flags().StringVar(&cloud.SSHKeyCreationParameters.Name, "name", "", "Name for the SSH key to create") sshKeyCreateCmd.Flags().StringVar(&cloud.SSHKeyCreationParameters.PublicKey, "public-key", "", "Public key for the SSH key to create") - sshKeyCreateCmd.Flags().StringVar(&cloud.SSHKeyCreationParameters.Region, "region", "", "Region for the SSH key to create (optional)") - addParameterFileFlags(sshKeyCreateCmd, false, assets.CloudOpenapiSchema, "/v1/cloud/project/{serviceName}/sshkey", "post", cloud.SSHKeyCreationExample, nil) + addParameterFileFlags(sshKeyCreateCmd, false, assets.CloudV2OpenapiSchema, "/publicCloud/project/{projectId}/sshKey", "post", cloud.SSHKeyCreationExample, nil) addInteractiveEditorFlag(sshKeyCreateCmd) markFlagsMutuallyExclusive(sshKeyCreateCmd, "from-file", "editor") sshKeyCmd.AddCommand(sshKeyCreateCmd) sshKeyCmd.AddCommand(&cobra.Command{ - Use: "delete ", + Use: "delete ", Short: "Delete a SSH key", Run: cloud.DeleteCloudSSHKey, Args: cobra.ExactArgs(1), diff --git a/internal/cmd/cloud_ssh_key_test.go b/internal/cmd/cloud_ssh_key_test.go new file mode 100644 index 00000000..e192f434 --- /dev/null +++ b/internal/cmd/cloud_ssh_key_test.go @@ -0,0 +1,93 @@ +// SPDX-FileCopyrightText: 2025 OVH SAS +// +// SPDX-License-Identifier: Apache-2.0 + +package cmd_test + +import ( + "net/http" + + "github.com/jarcoal/httpmock" + "github.com/maxatome/go-testdeep/td" + "github.com/maxatome/tdhttpmock" + "github.com/ovh/ovhcloud-cli/internal/cmd" +) + +func (ms *MockSuite) TestCloudSSHKeyListCmd(assert, require *td.T) { + httpmock.RegisterResponder(http.MethodGet, + "https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/sshKey", + httpmock.NewStringResponder(200, `[ + { + "name": "my-key", + "publicKey": "ssh-rsa AAAAB3Nza example-key", + "createdAt": "2025-01-01T00:00:00Z", + "updatedAt": "2025-01-02T00:00:00Z" + } + ]`)) + + out, err := cmd.Execute("cloud", "ssh-key", "list", "--cloud-project", "fakeProjectID") + + require.CmpNoError(err) + assert.Cmp(out, td.Contains("my-key")) + assert.Cmp(out, td.Contains("2025-01-01T00:00:00Z")) +} + +func (ms *MockSuite) TestCloudSSHKeyGetCmd(assert, require *td.T) { + httpmock.RegisterResponder(http.MethodGet, + "https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/sshKey/my-key", + httpmock.NewStringResponder(200, `{ + "name": "my-key", + "publicKey": "ssh-rsa AAAAB3Nza example-key", + "createdAt": "2025-01-01T00:00:00Z", + "updatedAt": "2025-01-02T00:00:00Z" + }`)) + + out, err := cmd.Execute("cloud", "ssh-key", "get", "my-key", "--cloud-project", "fakeProjectID") + + require.CmpNoError(err) + assert.Cmp(out, td.Contains("my-key")) + assert.Cmp(out, td.Contains("ssh-rsa AAAAB3Nza example-key")) +} + +func (ms *MockSuite) TestCloudSSHKeyCreateCmd(assert, require *td.T) { + httpmock.RegisterMatcherResponder(http.MethodPost, + "https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/sshKey", + tdhttpmock.JSONBody(td.JSON(` + { + "name": "my-key", + "publicKey": "ssh-rsa AAAAB3Nza example-key" + }`), + ), + httpmock.NewStringResponder(200, `{ + "name": "my-key", + "publicKey": "ssh-rsa AAAAB3Nza example-key" + }`), + ) + + out, err := cmd.Execute("cloud", "ssh-key", "create", "--cloud-project", "fakeProjectID", "--name", "my-key", "--public-key", "ssh-rsa AAAAB3Nza example-key") + + require.CmpNoError(err) + assert.Cmp(out, td.Contains("✅ SSH key successfully created")) +} + +func (ms *MockSuite) TestCloudSSHKeyDeleteCmd(assert, require *td.T) { + httpmock.RegisterResponder(http.MethodDelete, + "https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/sshKey/my-key", + httpmock.NewStringResponder(200, ``)) + + out, err := cmd.Execute("cloud", "ssh-key", "delete", "my-key", "--cloud-project", "fakeProjectID") + + require.CmpNoError(err) + assert.Cmp(out, td.Contains("✅ SSH key successfully deleted")) +} + +func (ms *MockSuite) TestCloudSSHKeyDeleteCmdError(assert, require *td.T) { + httpmock.RegisterResponder(http.MethodDelete, + "https://eu.api.ovh.com/v2/publicCloud/project/fakeProjectID/sshKey/missing-key", + httpmock.NewStringResponder(404, `{"class":"Client::NotFound::SSHKeyDoesNotExist","message":"SSH key not found"}`)) + + _, err := cmd.Execute("cloud", "ssh-key", "delete", "missing-key", "--cloud-project", "fakeProjectID") + + require.CmpError(err) + assert.Cmp(err.Error(), td.Contains("error deleting SSH key")) +} diff --git a/internal/services/cloud/cloud_ssh_key.go b/internal/services/cloud/cloud_ssh_key.go index 063caf11..840e234f 100644 --- a/internal/services/cloud/cloud_ssh_key.go +++ b/internal/services/cloud/cloud_ssh_key.go @@ -11,7 +11,6 @@ import ( "github.com/ovh/ovhcloud-cli/internal/assets" "github.com/ovh/ovhcloud-cli/internal/display" - filtersLib "github.com/ovh/ovhcloud-cli/internal/filters" "github.com/ovh/ovhcloud-cli/internal/flags" httpLib "github.com/ovh/ovhcloud-cli/internal/http" "github.com/ovh/ovhcloud-cli/internal/services/common" @@ -19,7 +18,7 @@ import ( ) var ( - cloudprojectSSHKeyColumnsToDisplay = []string{"id", "name", "regions"} + cloudprojectSSHKeyColumnsToDisplay = []string{"name", "createdAt", "updatedAt"} //go:embed templates/cloud_ssh_key.tmpl cloudSSHKeyTemplate string @@ -27,11 +26,10 @@ var ( //go:embed parameter-samples/ssh-key-create.json SSHKeyCreationExample string - // sshKeyCreationParameters holds the parameters for creating a new SSH key. + // SSHKeyCreationParameters holds the parameters for creating a new SSH key. SSHKeyCreationParameters struct { Name string `json:"name,omitempty"` PublicKey string `json:"publicKey,omitempty"` - Region string `json:"region,omitempty"` } ) @@ -41,21 +39,8 @@ func ListCloudSSHKeys(_ *cobra.Command, _ []string) { display.OutputError(&flags.OutputFormatConfig, "%s", err) return } - path := fmt.Sprintf("/v1/cloud/project/%s/sshkey", projectID) - var body []map[string]any - if err := httpLib.Client.Get(path, &body); err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to fetch SSH keys: %s", err) - return - } - - body, err = filtersLib.FilterLines(body, flags.GenericFilters) - if err != nil { - display.OutputError(&flags.OutputFormatConfig, "failed to filter results: %s", err) - return - } - - display.RenderTable(body, cloudprojectSSHKeyColumnsToDisplay, &flags.OutputFormatConfig) + common.ManageListRequestNoExpand(fmt.Sprintf("/v2/publicCloud/project/%s/sshKey", projectID), cloudprojectSSHKeyColumnsToDisplay, flags.GenericFilters) } func GetCloudSSHKey(_ *cobra.Command, args []string) { @@ -65,7 +50,7 @@ func GetCloudSSHKey(_ *cobra.Command, args []string) { return } - common.ManageObjectRequest(fmt.Sprintf("/v1/cloud/project/%s/sshkey", projectID), args[0], cloudSSHKeyTemplate) + common.ManageObjectRequest(fmt.Sprintf("/v2/publicCloud/project/%s/sshKey", projectID), args[0], cloudSSHKeyTemplate) } func CreateCloudSSHKey(cmd *cobra.Command, _ []string) { @@ -75,14 +60,14 @@ func CreateCloudSSHKey(cmd *cobra.Command, _ []string) { return } - endpoint := fmt.Sprintf("/v1/cloud/project/%s/sshkey", projectID) + endpoint := fmt.Sprintf("/v2/publicCloud/project/%s/sshKey", projectID) _, err = common.CreateResource( cmd, - "/v1/cloud/project/{serviceName}/sshkey", + "/publicCloud/project/{projectId}/sshKey", endpoint, SSHKeyCreationExample, SSHKeyCreationParameters, - assets.CloudOpenapiSchema, + assets.CloudV2OpenapiSchema, []string{"name", "publicKey"}, ) if err != nil { @@ -100,7 +85,7 @@ func DeleteCloudSSHKey(_ *cobra.Command, args []string) { return } - endpoint := fmt.Sprintf("/v1/cloud/project/%s/sshkey/%s", projectID, url.PathEscape(args[0])) + endpoint := fmt.Sprintf("/v2/publicCloud/project/%s/sshKey/%s", projectID, url.PathEscape(args[0])) if err := httpLib.Client.Delete(endpoint, nil); err != nil { display.OutputError(&flags.OutputFormatConfig, "error deleting SSH key %q: %s", args[0], err) diff --git a/internal/services/cloud/templates/cloud_ssh_key.tmpl b/internal/services/cloud/templates/cloud_ssh_key.tmpl index 3d7f65ec..9c66ae71 100644 --- a/internal/services/cloud/templates/cloud_ssh_key.tmpl +++ b/internal/services/cloud/templates/cloud_ssh_key.tmpl @@ -3,14 +3,12 @@ ## General information -**Name**: {{index .Result "name"}} -**Fingerprint**: {{index .Result "fingerPrint"}} - -**Regions**:{{range index .Result "regions"}} -- {{.}}{{end}} +**Name**: {{index .Result "name"}} +**Creation date**: {{index .Result "createdAt"}} +**Update date**: {{index .Result "updatedAt"}} ## Public key {{index .Result "publicKey"}} -💡 Use option -o json or -o yaml to get the raw output with all information \ No newline at end of file +💡 Use option -o json or -o yaml to get the raw output with all information