Skip to content

Commit

Permalink
Add example playbooks (#24)
Browse files Browse the repository at this point in the history
* Fix placement request not entirely saved in DB

* Return credentials with GET placements/{uuid}

The credentials are needed by anarchy when the placement already exists.
Decrypt and return it.

* authorize only admins to GET all placements

All API endpoints are protected, but as a mesure of security, allow only
Admins to list all placements.

* Add playbook example to Get a sandbox

* Add dedicated function LoadResourcesWithCreds

Keep LoadResources() method empty from any credential

* Make get.yml idempotent

* Add release playbook

* Add annotations (guid, envtype, email, owner, ..)

* Make release.yml idempotent
  • Loading branch information
fridim authored Jun 28, 2023
1 parent 731fdd2 commit 582ecaf
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 12 deletions.
4 changes: 2 additions & 2 deletions cmd/sandbox-api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ func (h *BaseHandler) CreatePlacementHandler(w http.ResponseWriter, r *http.Requ
Placement: models.Placement{
ServiceUuid: placementRequest.ServiceUuid,
Annotations: placementRequest.Annotations,
Request: v1.PlacementRequest{Resources: placementRequest.Resources},
Request: placementRequest,
},
}
placement.Resources = resources
Expand Down Expand Up @@ -255,7 +255,7 @@ func (h *BaseHandler) GetPlacementHandler(w http.ResponseWriter, r *http.Request
log.Logger.Error("GetPlacementHandler", "error", err)
return
}
placement.LoadResources(h.accountProvider)
placement.LoadResourcesWithCreds(h.accountProvider)

w.WriteHeader(http.StatusOK)
render.Render(w, r, placement)
Expand Down
2 changes: 1 addition & 1 deletion cmd/sandbox-api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,6 @@ func main() {
r.Put("/api/v1/accounts/{kind}/{account}/status", baseHandler.LifeCycleAccountHandler("status"))
r.Get("/api/v1/accounts/{kind}/{account}/status", baseHandler.GetStatusAccountHandler)
r.Post("/api/v1/placements", baseHandler.CreatePlacementHandler)
r.Get("/api/v1/placements", baseHandler.GetPlacementsHandler)
r.Get("/api/v1/placements/{uuid}", baseHandler.GetPlacementHandler)
r.Delete("/api/v1/placements/{uuid}", baseHandler.DeletePlacementHandler)
})
Expand All @@ -208,6 +207,7 @@ func main() {
// ---------------------------------
// Routes
// ---------------------------------
r.Get("/api/v1/placements", baseHandler.GetPlacementsHandler)
r.Post("/api/v1/admin/jwt", adminHandler.IssueLoginJWTHandler)
r.Get("/api/v1/admin/jwt", baseHandler.GetJWTHandler)
r.Put("/api/v1/admin/jwt/{id}/invalidate", baseHandler.InvalidateTokenHandler)
Expand Down
1 change: 1 addition & 0 deletions docs/api-reference/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ paths:
summary: Get all placements
description: |
The placements are returned.
Only Admins are authorized to get all placements.
NOTE: the resources are not returned because that would generate too much data and take too much time. To get the information about accounts for a particular placement, query the `/placements/{uuid}` endpoint.
responses:
Expand Down
113 changes: 113 additions & 0 deletions docs/examples/ansible/get.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
# See https://rhpds.github.io/sandbox/api-reference/

- name: Get a placement using uuid
hosts: localhost
gather_facts: false
vars:
sandbox_api_url: http://localhost:8080
tasks:
- name: Ensure needed variables are set
assert:
that: "{{ check.that }}"
fail_msg: "{{ check.msg }}"
loop_control:
loop_var: check
label: "{{ check.msg }}"
loop:
- msg: sandbox_api_login_token must be provided
that: sandbox_api_login_token is defined
- msg: sandbox_api_url must be provided
that: sandbox_api_url is defined
- msg: uuid is not defined
that: uuid is defined

- name: Login using the JWT login token
uri:
url: "{{ sandbox_api_url }}/api/v1/login"
headers:
Authorization: Bearer {{ sandbox_api_login_token }}
register: r_login

- name: Save access token
set_fact:
access_token: "{{ r_login.json.access_token }}"

- name: Check if placement exists
uri:
headers:
Authorization: Bearer {{ access_token }}
url: "{{ sandbox_api_url }}/api/v1/placements/{{ uuid }}"
method: GET
status_code: [200, 404]
register: r_get_placement

- name: Set placement
set_fact:
placement: "{{ r_get_placement.json }}"
when: r_get_placement.status == 200

- when: r_get_placement.status == 404
block:
- name: Get a placement, book 1 aws sandbox
when: r_get_placement.status == 404
uri:
headers:
Authorization: Bearer {{ access_token }}
url: "{{ sandbox_api_url }}/api/v1/placements"
method: POST
body_format: json
body:
service_uuid: "{{ uuid }}"
annotations:
guid: "abcde"
env_type: "ocp4-cluster"
owner: "user"
owner_email: "user@example.com"
comment: "Created by Ansible"
resources:
- kind: AwsSandbox
count: 1
register: r_new_placement

- name: Save placement
set_fact:
placement: "{{ r_new_placement.json.Placement }}"


- set_fact:
sandbox_name: "{{ placement.resources[0].name }}"
sandbox_zone: "{{ placement.resources[0].zone }}"
sandbox_hosted_zone_id: "{{ placement.resources[0].hosted_zone_id }}"
sandbox_account: "{{ placement.resources[0].account_id }}"
sandbox_account_id: "{{ placement.resources[0].account_id }}"
sandbox_aws_access_key_id: >-
{{ (placement.resources[0].credentials
| selectattr('kind', 'equalto', 'aws_iam_key')
| selectattr('name', 'equalto', 'admin-key')
| first
).get('aws_access_key_id') }}
sandbox_aws_secret_access_key: >-
{{ (placement.resources[0].credentials
| selectattr('kind', 'equalto', 'aws_iam_key')
| selectattr('name', 'equalto', 'admin-key')
| first).get('aws_secret_access_key') }}
- name: Save secret of aws_sandbox_secrets dictionary
set_fact:
aws_sandbox_secrets:
sandbox_aws_access_key_id: "{{ sandbox_aws_access_key_id }}"
sandbox_aws_secret_access_key: "{{ sandbox_aws_secret_access_key }}"
sandbox_hosted_zone_id: "{{ sandbox_hosted_zone_id }}"
sandbox_name: "{{ sandbox_name }}"
sandbox_account: "{{ sandbox_account }}"
sandbox_account_id: "{{ sandbox_account_id }}"
sandbox_zone: "{{ sandbox_zone }}"
# agnosticd
aws_access_key_id: "{{ sandbox_aws_access_key_id }}"
aws_secret_access_key: "{{ sandbox_aws_secret_access_key }}"
HostedZoneId: "{{ sandbox_hosted_zone_id }}"
subdomain_base_suffix: ".{{ sandbox_zone }}"

- debug:
var: aws_sandbox_secrets
45 changes: 45 additions & 0 deletions docs/examples/ansible/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# See https://rhpds.github.io/sandbox/api-reference/

- name: Release a placement using uuid
hosts: localhost
gather_facts: false
vars:
sandbox_api_url: http://localhost:8080
tasks:
- name: Ensure needed variables are set
assert:
that: "{{ check.that }}"
fail_msg: "{{ check.msg }}"
loop_control:
loop_var: check
label: "{{ check.msg }}"
loop:
- msg: sandbox_api_login_token must be provided
that: sandbox_api_login_token is defined
- msg: sandbox_api_url must be provided
that: sandbox_api_url is defined
- msg: uuid is not defined
that: uuid is defined

- name: Login using the JWT login token
uri:
url: "{{ sandbox_api_url }}/api/v1/login"
headers:
Authorization: Bearer {{ sandbox_api_login_token }}
register: r_login

- name: Save access token
set_fact:
access_token: "{{ r_login.json.access_token }}"

- name: Release placement
uri:
headers:
Authorization: Bearer {{ access_token }}
url: "{{ sandbox_api_url }}/api/v1/placements/{{ uuid }}"
method: DELETE
status_code: [200, 404]
register: r_placement

- debug:
var: r_placement
42 changes: 34 additions & 8 deletions internal/dynamodb/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,20 +148,35 @@ func makeAccount(account AwsAccountDynamoDB) models.AwsAccount {
return a
}

// makeAccount creates new models.AwsAccountWithCreds from AwsAccountDynamoDB
func makeAccountWithCreds(account AwsAccountDynamoDB) models.AwsAccountWithCreds {

// makeAccounts creates new []models.AwsAccount from []AwsAccountDynamoDB
func makeAccounts(accounts []AwsAccountDynamoDB) []models.AwsAccount {
r := []models.AwsAccount{}
for _, account := range accounts {
r = append(r, makeAccount(account))
}

return r
}
// makeAccountWithCreds creates new models.AwsAccountWithCreds from AwsAccountDynamoDB
func (provider *AwsAccountDynamoDBProvider) makeAccountWithCreds(account AwsAccountDynamoDB) models.AwsAccountWithCreds {

a := makeAccount(account)

result := models.AwsAccountWithCreds{
AwsAccount: a,
}

decrypted, err := provider.DecryptSecret(account.AwsSecretAccessKey)
if err != nil {
decrypted = account.AwsSecretAccessKey
}

iamKey := models.AwsIamKey{
Kind: "aws_iam_key",
Name: "admin-key",
AwsAccessKeyID: account.AwsAccessKeyID,
AwsSecretAccessKey: account.AwsSecretAccessKey,
AwsSecretAccessKey: decrypted,
}

// For now, an account only has one credential: an IAM key
Expand All @@ -170,11 +185,11 @@ func makeAccountWithCreds(account AwsAccountDynamoDB) models.AwsAccountWithCreds
return result
}

// makeAccounts creates new []models.AwsAccount from []AwsAccountDynamoDB
func makeAccounts(accounts []AwsAccountDynamoDB) []models.AwsAccount {
r := []models.AwsAccount{}
// makeAccountsWithCreds creates new []models.AwsAccountWithCreds from []AwsAccountDynamoDB
func (provider *AwsAccountDynamoDBProvider) makeAccountsWithCreds(accounts []AwsAccountDynamoDB) []models.AwsAccountWithCreds {
r := []models.AwsAccountWithCreds{}
for _, account := range accounts {
r = append(r, makeAccount(account))
r = append(r, provider.makeAccountWithCreds(account))
}

return r
Expand Down Expand Up @@ -340,6 +355,16 @@ func (a *AwsAccountDynamoDBProvider) FetchAllByServiceUuid(serviceUuid string) (
return makeAccounts(accounts), nil
}

// FetchAllByServiceUuid returns the list of accounts from dynamodb for a specific service uuid
func (a *AwsAccountDynamoDBProvider) FetchAllByServiceUuidWithCreds(serviceUuid string) ([]models.AwsAccountWithCreds, error) {
filter := expression.Name("service_uuid").Equal(expression.Value(serviceUuid))
accounts, err := GetAccounts(a.Svc, filter, -1)
if err != nil {
return []models.AwsAccountWithCreds{}, err
}
return a.makeAccountsWithCreds(accounts), nil
}

// FetchAllToCleanup returns the list of accounts from dynamodb
func (a *AwsAccountDynamoDBProvider) FetchAllToCleanup() ([]models.AwsAccount, error) {
filter := expression.Name("to_cleanup").Equal(expression.Value(true))
Expand Down Expand Up @@ -482,7 +507,7 @@ func (a *AwsAccountDynamoDBProvider) Request(service_uuid string, count int, ann
return []models.AwsAccountWithCreds{}, err
}
booked.AwsSecretAccessKey = strings.Trim(booked.AwsSecretAccessKey, "\n\r\t ")
bookedAccounts = append(bookedAccounts, makeAccountWithCreds(booked))
bookedAccounts = append(bookedAccounts, a.makeAccountWithCreds(booked))
count = count - 1
if count == 0 {
break
Expand Down Expand Up @@ -558,5 +583,6 @@ func (a *AwsAccountDynamoDBProvider) DecryptSecret(encrypted string) (string, er
if err != nil {
return "", err
}
str = strings.Trim(string(str), "\r\n\t ")
return str, nil
}
1 change: 1 addition & 0 deletions internal/models/aws_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type AwsAccountProvider interface {
FetchAllToCleanup() ([]AwsAccount, error)
FetchAllSorted(by string) ([]AwsAccount, error)
FetchAllByServiceUuid(serviceUuid string) ([]AwsAccount, error)
FetchAllByServiceUuidWithCreds(serviceUuid string) ([]AwsAccountWithCreds, error)
Request(service_uuid string, count int, annotations map[string]string) ([]AwsAccountWithCreds, error)
MarkForCleanup(name string) error
MarkForCleanupByServiceUuid(serviceUuid string) error
Expand Down
17 changes: 17 additions & 0 deletions internal/models/placements.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ func (p *Placement) LoadResources(accountProvider AwsAccountProvider) error {
return nil
}

func (p *Placement) LoadResourcesWithCreds(accountProvider AwsAccountProvider) error {

accounts, err := accountProvider.FetchAllByServiceUuidWithCreds(p.ServiceUuid)

if err != nil {
return err
}

p.Resources = []any{}

for _, account := range accounts {
p.Resources = append(p.Resources, account)
}

return nil
}

func (p *Placement) Save(dbpool *pgxpool.Pool) error {
var id int
// Check if placement already exists in the DB
Expand Down
3 changes: 2 additions & 1 deletion todo.org
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
** DONE create golang channel for stop/start
** DONE parameterize the number of concurrent workers
** TODO create lifecycle handler for placements
* TODO OpenShift limit and req for pods
* DONE OpenShift limit and req for pods
* TODO patch clients (sandbox-list, mark_for_cleanup script, etc) to use the sandbox-API instead of dynamodb
* TODO unit tests and fixture/functional tests
* TODO documentation coverage
* TODO move handlers per version?
Expand Down

0 comments on commit 582ecaf

Please sign in to comment.