Skip to content

Commit

Permalink
Feat: rotating SSH Key Value causes a replace (with new ID) as oppose…
Browse files Browse the repository at this point in the history
…d to just edit-in-place (#752)
  • Loading branch information
TomerHeber committed Nov 22, 2023
1 parent 9b5c4df commit 7ef0e10
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 4 deletions.
1 change: 1 addition & 0 deletions client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type ApiClientInterface interface {
VariablesFromRepository(payload *VariablesFromRepositoryPayload) ([]ConfigurationVariable, error)
SshKeys() ([]SshKey, error)
SshKeyCreate(payload SshKeyCreatePayload) (*SshKey, error)
SshKeyUpdate(id string, payload *SshKeyUpdatePayload) (*SshKey, error)
SshKeyDelete(id string) error
CredentialsCreate(request CredentialCreatePayload) (Credentials, error)
CredentialsUpdate(id string, request CredentialCreatePayload) (Credentials, error)
Expand Down
15 changes: 15 additions & 0 deletions client/api_client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions client/sshkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type SshKeyCreatePayload struct {
Value string `json:"value"`
}

type SshKeyUpdatePayload struct {
Value string `json:"value"`
}

func (client *ApiClient) SshKeyCreate(payload SshKeyCreatePayload) (*SshKey, error) {
organizationId, err := client.OrganizationId()
if err != nil {
Expand All @@ -32,6 +36,15 @@ func (client *ApiClient) SshKeyCreate(payload SshKeyCreatePayload) (*SshKey, err
return &result, nil
}

func (client *ApiClient) SshKeyUpdate(id string, payload *SshKeyUpdatePayload) (*SshKey, error) {
var result SshKey

if err := client.http.Put("/ssh-keys/"+id, payload, &result); err != nil {
return nil, err
}
return &result, nil
}

func (client *ApiClient) SshKeyDelete(id string) error {
return client.http.Delete("/ssh-keys/"+id, nil)
}
Expand Down
33 changes: 33 additions & 0 deletions client/sshkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,37 @@ var _ = Describe("SshKey", func() {
Expect(sshKeys).Should(ContainElement(mockSshKey))
})
})

Describe("SshKetUpdate", func() {
Describe("Success", func() {
updateMockSshKey := mockSshKey
updateMockSshKey.Value = "new-value"
var updatedSshKey *SshKey
var err error

BeforeEach(func() {
updateSshKeyPayload := SshKeyUpdatePayload{Value: updateMockSshKey.Value}

httpCall = mockHttpClient.EXPECT().
Put("/ssh-keys/"+mockSshKey.Id, &updateSshKeyPayload, gomock.Any()).
Do(func(path string, request interface{}, response *SshKey) {
*response = updateMockSshKey
})

updatedSshKey, err = apiClient.SshKeyUpdate(mockSshKey.Id, &updateSshKeyPayload)
})

It("Should send Put request with expected payload", func() {
httpCall.Times(1)
})

It("Should not return an error", func() {
Expect(err).To(BeNil())
})

It("Should return ssh key received from API", func() {
Expect(*updatedSshKey).To(Equal(updateMockSshKey))
})
})
})
})
17 changes: 16 additions & 1 deletion env0/resource_sshkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func resourceSshKey() *schema.Resource {
return &schema.Resource{
CreateContext: resourceSshKeyCreate,
ReadContext: resourceSshKeyRead,
UpdateContext: resourceSshKeyUpdate,
DeleteContext: resourceSshKeyDelete,

Importer: &schema.ResourceImporter{StateContext: resourceSshKeyImport},
Expand All @@ -29,7 +30,6 @@ func resourceSshKey() *schema.Resource {
Type: schema.TypeString,
Description: "value is a private key in PEM format (first line usually looks like -----BEGIN OPENSSH PRIVATE KEY-----)",
Required: true,
ForceNew: true,
Sensitive: true,
},
},
Expand All @@ -54,6 +54,21 @@ func resourceSshKeyCreate(ctx context.Context, d *schema.ResourceData, meta inte
return nil
}

func resourceSshKeyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(client.ApiClientInterface)

var payload client.SshKeyUpdatePayload
if err := readResourceData(&payload, d); err != nil {
return diag.Errorf("schema resource data deserialization failed: %v", err)
}

if _, err := apiClient.SshKeyUpdate(d.Id(), &payload); err != nil {
return diag.Errorf("could not update ssh key: %v", err)
}

return nil
}

func resourceSshKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
sshKey, err := getSshKeyById(d.Id(), meta)
if err != nil {
Expand Down
31 changes: 28 additions & 3 deletions env0/resource_sshkey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,25 @@ func TestUnitSshKeyResource(t *testing.T) {
resourceType := "env0_ssh_key"
resourceName := "test"
accessor := resourceAccessor(resourceType, resourceName)

sshKey := &client.SshKey{
Id: "id0",
Name: "name0",
Value: "Key🔑",
}

updatedSshKey := *sshKey
updatedSshKey.Value = "new-valuw"

sshKeyCreatePayload := client.SshKeyCreatePayload{
Name: sshKey.Name,
Value: sshKey.Value,
}

sshKeyUpdatePayload := client.SshKeyUpdatePayload{
Value: updatedSshKey.Value,
}

t.Run("Success", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
Expand All @@ -35,13 +45,28 @@ func TestUnitSshKeyResource(t *testing.T) {
resource.TestCheckResourceAttr(accessor, "value", sshKey.Value),
),
},
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": updatedSshKey.Name,
"value": updatedSshKey.Value,
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", sshKey.Id),
resource.TestCheckResourceAttr(accessor, "name", updatedSshKey.Name),
resource.TestCheckResourceAttr(accessor, "value", updatedSshKey.Value),
),
},
},
}

runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {
mock.EXPECT().SshKeyCreate(sshKeyCreatePayload).Times(1).Return(sshKey, nil)
mock.EXPECT().SshKeys().Times(1).Return([]client.SshKey{*sshKey}, nil)
mock.EXPECT().SshKeyDelete(sshKey.Id).Times(1).Return(nil)
gomock.InOrder(
mock.EXPECT().SshKeyCreate(sshKeyCreatePayload).Times(1).Return(sshKey, nil),
mock.EXPECT().SshKeys().Times(2).Return([]client.SshKey{*sshKey}, nil),
mock.EXPECT().SshKeyUpdate(sshKey.Id, &sshKeyUpdatePayload).Times(1).Return(&updatedSshKey, nil),
mock.EXPECT().SshKeys().Times(1).Return([]client.SshKey{updatedSshKey}, nil),
mock.EXPECT().SshKeyDelete(sshKey.Id).Times(1).Return(nil),
)
})
})

Expand Down

0 comments on commit 7ef0e10

Please sign in to comment.