Skip to content

Commit

Permalink
feat(web): Expose experimental account storage API (#1494)
Browse files Browse the repository at this point in the history
* feat(web): Expose experimental account storage API

This adds some of the REST APIs introduced in the experimental account
storage API in Clouddriver to Gate. Initially, these APIs are only
available for admins.

* Combine account and credentials endpoints

* Add docs on AccountDefinition

* Add alpha annotations
  • Loading branch information
jvz authored Jan 7, 2022
1 parent c25eef9 commit 11d7ce6
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.netflix.spinnaker.kork.plugins.SpinnakerPluginDescriptor;
import java.util.ArrayList;
import java.util.Collection;
Expand All @@ -12,6 +13,7 @@
import java.util.Map;
import retrofit.client.Response;
import retrofit.http.Body;
import retrofit.http.DELETE;
import retrofit.http.GET;
import retrofit.http.Headers;
import retrofit.http.POST;
Expand All @@ -31,6 +33,21 @@ public interface ClouddriverService {
@GET("/credentials/{account}")
AccountDetails getAccount(@Path("account") String account);

@GET("/credentials/type/{type}")
List<AccountDefinition> getAccountDefinitionsByType(
@Path("type") String type,
@Query("limit") Integer limit,
@Query("startingAccountName") String startingAccountName);

@POST("/credentials")
AccountDefinition createAccountDefinition(@Body AccountDefinition accountDefinition);

@PUT("/credentials")
AccountDefinition updateAccountDefinition(@Body AccountDefinition accountDefinition);

@DELETE("/credentials/{account}")
void deleteAccountDefinition(@Path("account") String account);

@GET("/task/{taskDetailsId}")
Map getTaskDetails(@Path("taskDetailsId") String taskDetailsId);

Expand Down Expand Up @@ -493,4 +510,39 @@ public void setCloudProvider(String cloudProvider) {
private String cloudProvider;
private final Map<String, Object> details = new HashMap<String, Object>();
}

/**
* Wrapper type for Clouddriver account definitions. Clouddriver account definitions implement
* {@code CredentialsDefinition}, and its type discriminator is present in a property named
* {@code @type}. An instance of an account definition may have fairly different properties than
* its corresponding {@code AccountCredentials} instance. Account definitions must store all the
* relevant properties unchanged while {@link Account} and {@link AccountDetails} may summarize
* and remove data returned from their corresponding APIs. Account definitions must be transformed
* by a {@code CredentialsParser} before their corresponding credentials may be used by
* Clouddriver.
*/
class AccountDefinition {
private final Map<String, Object> details = new HashMap<>();
private String type;

@JsonAnyGetter
public Map<String, Object> details() {
return details;
}

@JsonAnySetter
public void set(String name, Object value) {
details.put(name, value);
}

@JsonProperty("@type")
public String getType() {
return type;
}

@JsonProperty("@type")
public void setType(String type) {
this.type = type;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,22 @@ import com.fasterxml.jackson.databind.ObjectMapper
import com.netflix.spinnaker.gate.security.AllowedAccountsSupport
import com.netflix.spinnaker.gate.security.SpinnakerUser
import com.netflix.spinnaker.gate.services.AccountLookupService
import com.netflix.spinnaker.gate.services.internal.ClouddriverService
import com.netflix.spinnaker.gate.services.internal.ClouddriverService.Account
import com.netflix.spinnaker.gate.services.internal.ClouddriverService.AccountDetails
import com.netflix.spinnaker.kork.annotations.Alpha
import com.netflix.spinnaker.security.User
import io.swagger.annotations.ApiOperation
import io.swagger.annotations.ApiParam
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.access.prepost.PostFilter
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.DeleteMapping
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
Expand All @@ -43,6 +53,9 @@ class CredentialsController {
@Autowired
AllowedAccountsSupport allowedAccountsSupport

@Autowired
ClouddriverService clouddriverService

@Autowired
ObjectMapper objectMapper

Expand Down Expand Up @@ -78,4 +91,53 @@ class CredentialsController {
@RequestHeader(value = "X-RateLimit-App", required = false) String sourceApp) {
return getAccountDetailsWithAuthorizedFlag(user).find { it.name == account }
}

@GetMapping('/type/{accountType}')
@ApiOperation('Looks up account definitions by type.')
@PostFilter("hasPermission(filterObject.name, 'ACCOUNT', 'WRITE')")
@Alpha
List<ClouddriverService.AccountDefinition> getAccountsByType(
@ApiParam(value = 'Value of the "@type" key for accounts to search for.', example = 'kubernetes')
@PathVariable String accountType,
@ApiParam('Maximum number of entries to return in results. Used for pagination.')
@RequestParam OptionalInt limit,
@ApiParam('Account name to start account definition listing from. Used for pagination.')
@RequestParam Optional<String> startingAccountName
) {
clouddriverService.getAccountDefinitionsByType(accountType, limit.isPresent() ? limit.getAsInt() : null, startingAccountName.orElse(null))
}

@PostMapping
@ApiOperation('Creates a new account definition.')
@PreAuthorize('isAuthenticated()')
@Alpha
ClouddriverService.AccountDefinition createAccount(
@ApiParam('Account definition body including a discriminator field named "@type" with the account type.')
@RequestBody ClouddriverService.AccountDefinition accountDefinition
) {
clouddriverService.createAccountDefinition(accountDefinition)
}

@PutMapping
@ApiOperation('Updates an existing account definition.')
@PreAuthorize("hasPermission(#definition.name, 'ACCOUNT', 'WRITE')")
@Alpha
ClouddriverService.AccountDefinition updateAccount(
@ApiParam('Account definition body including a discriminator field named "@type" with the account type.')
@RequestBody ClouddriverService.AccountDefinition accountDefinition
) {
clouddriverService.updateAccountDefinition(accountDefinition)
}

@DeleteMapping('/{accountName}')
@ApiOperation(value = 'Deletes an account definition by name.',
notes = 'Deleted accounts can be restored via the update API. Previously deleted accounts cannot be "created" again to avoid conflicts with existing pipelines.')
@PreAuthorize("hasPermission(#definition.name, 'ACCOUNT', 'WRITE')")
@Alpha
void deleteAccount(
@ApiParam('Name of account definition to delete.')
@PathVariable String accountName
) {
clouddriverService.deleteAccountDefinition(accountName)
}
}

0 comments on commit 11d7ce6

Please sign in to comment.