diff --git a/Makefile b/Makefile index c6a0552..5948dbe 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,10 @@ test: install-test-reqs @echo ">> running all tests" @$(GO) test $(pkgs) +test-integration: install-test-reqs + @echo ">> running integration tests" + @$(GO) test -tags=integration $(pkgs) + lint: install-test-reqs @gometalinter --vendor --disable-all --enable=gosimple --enable=golint --enable=vet --enable=ineffassign --enable=unconvert \ --exclude="by other packages, and that stutters; consider calling this" \ diff --git a/awx.go b/awx.go index c43b64e..8b11db5 100644 --- a/awx.go +++ b/awx.go @@ -25,6 +25,8 @@ type AWX struct { UserService *UserService GroupService *GroupService HostService *HostService + OrganizationService *OrganizationService + TeamService *TeamService } // Client implement http client. @@ -106,5 +108,9 @@ func NewAWX(baseURL, userName, passwd string, client *http.Client) *AWX { HostService: &HostService{ client: awxClient, }, + OrganizationService: &OrganizationService{ + client: awxClient, + }, + TeamService: &TeamService{client: awxClient}, } } diff --git a/awxtesting/dockerized.sh b/awxtesting/dockerized.sh new file mode 100755 index 0000000..709d472 --- /dev/null +++ b/awxtesting/dockerized.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Instantiate an AWX server using the AWX Installer: +# - This installer is powered by docker-composer +# - AWX will be available as http://localhost:80 + +rc=$(docker 2&>/dev/null; echo $?) +if [[ $rc -ne 0 ]]; then + echo "You MUST have docker installed" +fi + +git clone https://github.com/ansible/awx.git + +ansible-playbook -i awx/installer/inventory awx/installer/install.yml diff --git a/awxtesting/mockserver/mockdata/inventories.go b/awxtesting/mockserver/mockdata/inventories.go index cf342ef..1b006fe 100644 --- a/awxtesting/mockserver/mockdata/inventories.go +++ b/awxtesting/mockserver/mockdata/inventories.go @@ -15,21 +15,21 @@ var ( "related": { "created_by": "/api/v2/users/1/", "modified_by": "/api/v2/users/1/", - "job_templates": "/api/v2/inventories/1/job_templates/", - "variable_data": "/api/v2/inventories/1/variable_data/", + "hosts": "/api/v2/inventories/1/hosts/", + "groups": "/api/v2/inventories/1/groups/", "root_groups": "/api/v2/inventories/1/root_groups/", - "object_roles": "/api/v2/inventories/1/object_roles/", - "ad_hoc_commands": "/api/v2/inventories/1/ad_hoc_commands/", + "variable_data": "/api/v2/inventories/1/variable_data/", "script": "/api/v2/inventories/1/script/", "tree": "/api/v2/inventories/1/tree/", - "access_list": "/api/v2/inventories/1/access_list/", + "inventory_sources": "/api/v2/inventories/1/inventory_sources/", + "update_inventory_sources": "/api/v2/inventories/1/update_inventory_sources/", "activity_stream": "/api/v2/inventories/1/activity_stream/", + "job_templates": "/api/v2/inventories/1/job_templates/", + "ad_hoc_commands": "/api/v2/inventories/1/ad_hoc_commands/", + "access_list": "/api/v2/inventories/1/access_list/", + "object_roles": "/api/v2/inventories/1/object_roles/", "instance_groups": "/api/v2/inventories/1/instance_groups/", - "hosts": "/api/v2/inventories/1/hosts/", - "groups": "/api/v2/inventories/1/groups/", "copy": "/api/v2/inventories/1/copy/", - "update_inventory_sources": "/api/v2/inventories/1/update_inventory_sources/", - "inventory_sources": "/api/v2/inventories/1/inventory_sources/", "organization": "/api/v2/organizations/1/" }, "summary_fields": { @@ -51,37 +51,37 @@ var ( "last_name": "" }, "object_roles": { - "use_role": { - "id": 23, - "description": "Can use the inventory in a job template", - "name": "Use" - }, "admin_role": { - "id": 21, "description": "Can manage all aspects of the inventory", - "name": "Admin" + "name": "Admin", + "id": 21 + }, + "update_role": { + "description": "May update project or inventory or group using the configured source update system", + "name": "Update", + "id": 24 }, "adhoc_role": { - "id": 20, "description": "May run ad hoc commands on an inventory", - "name": "Ad Hoc" + "name": "Ad Hoc", + "id": 20 }, - "update_role": { - "id": 24, - "description": "May update project or inventory or group using the configured source update system", - "name": "Update" + "use_role": { + "description": "Can use the inventory in a job template", + "name": "Use", + "id": 23 }, "read_role": { - "id": 22, "description": "May view settings for the inventory", - "name": "Read" + "name": "Read", + "id": 22 } }, "user_capabilities": { "edit": true, + "delete": true, "copy": true, - "adhoc": true, - "delete": true + "adhoc": true } }, "created": "2018-05-21T01:34:35.657185Z", @@ -96,7 +96,6 @@ var ( "total_hosts": 2, "hosts_with_active_failures": 0, "total_groups": 0, - "groups_with_active_failures": 0, "has_inventory_sources": false, "total_inventory_sources": 0, "inventory_sources_with_failures": 0, diff --git a/credentials.go b/credentials.go new file mode 100644 index 0000000..61ebc90 --- /dev/null +++ b/credentials.go @@ -0,0 +1,103 @@ +package awx + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// CredentialService implements awx Credentials apis. +type CredentialService struct { + client *Client +} + +// ListCredentialsResponse represents `ListCredentials` endpoint response. +type ListCredentialsResponse struct { + Pagination + Results []*Credential `json:"results"` +} + +// ListCredentials shows list of awx Credentials. +func (t *CredentialService) ListCredentials(params map[string]string) ([]*Credential, *ListCredentialsResponse, error) { + result := new(ListCredentialsResponse) + endpoint := "/api/v2/credentials/" + resp, err := t.client.Requester.GetJSON(endpoint, result, params) + if err != nil { + return nil, result, err + } + + if err := CheckResponse(resp); err != nil { + return nil, result, err + } + + return result.Results, result, nil +} + +// CreateCredential creates an awx Credential. +func (t *CredentialService) CreateCredential(data map[string]interface{}, params map[string]string) (*Credential, error) { + mandatoryFields = []string{"name", "credential_type"} + validate, status := ValidateParams(data, mandatoryFields) + + if !status { + err := fmt.Errorf("Mandatory input arguments are absent: %s", validate) + return nil, err + } + + result := new(Credential) + endpoint := "/api/v2/credentials/" + payload, err := json.Marshal(data) + if err != nil { + return nil, err + } + + // Add check if Credential exists and return proper error + + resp, err := t.client.Requester.PostJSON(endpoint, bytes.NewReader(payload), result, params) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} + +// UpdateCredential update an awx user. +func (t *CredentialService) UpdateCredential(id int, data map[string]interface{}, params map[string]string) (*Credential, error) { + result := new(Credential) + endpoint := fmt.Sprintf("/api/v2/credentials/%d", id) + payload, err := json.Marshal(data) + if err != nil { + return nil, err + } + + resp, err := t.client.Requester.PatchJSON(endpoint, bytes.NewReader(payload), result, params) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} + +// DeleteCredential delete an awx Credential. +func (t *CredentialService) DeleteCredential(id int) (*Credential, error) { + result := new(Credential) + endpoint := fmt.Sprintf("/api/v2/credentials/%d", id) + + resp, err := t.client.Requester.Delete(endpoint, result, nil) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} diff --git a/credentials_test.go b/credentials_test.go new file mode 100644 index 0000000..5fc8dbc --- /dev/null +++ b/credentials_test.go @@ -0,0 +1,285 @@ +package awx + +// TODO Add mock data and then enable test +/* +import ( + "testing" + "time" +) + +func TestListCredentials(t *testing.T) { + var ( + expectListCredentialsResponse = []*Credential{ + { + ID: 1, + Type: "credential", + URL: "/api/v2/credentials/1/", + Related: &Related{ + ObjectRoles: "/api/v2/credentials/1/object_roles/", + AccessList: "/api/v2/credentials/1/access_list/", + CredentialType: "/api/v2/credential_types/1/", + ModifiedBy: "/api/v2/users/4/", + OwnerUsers: "/api/v2/credentials/1/owner_users/", + OwnerTeams: "/api/v2/credentials/1/owner_teams/", + Organization: "/api/v2/organizations/1/", + Copy: "/api/v2/credentials/1/copy/", + ActivityStream: "/api/v2/credentials/1/activity_stream/", + }, + SummaryFields: &Summary{ + Organization: &OrganizationSummary{ + ID: 1, + Name: "Default", + Description: "", + }, + Project: &Project{}, + ModifiedBy: &ByUserSummary{ + ID: 4, + Username: "admin", + FirstName: "", + LastName: "", + }, + ObjectRoles: &ObjectRoles{ + AdminRole: &ObjectRole{ + ID: 18, + Description: "Can manage all aspects of the credential", + Name: "Admin", + }, + UseRole: &ObjectRole{ + ID: 20, + Description: "Can use the credential in a job template", + Name: "Use", + }, + ReadRole: &ObjectRole{ + ID: 19, + Description: "May view settings for the credential", + Name: "Read", + }, + }, + UserCapabilities: &UserCapabilities{ + Edit: true, + Delete: true, + Copy: true, + }, + }, + Created: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-01T12:10:00.496424Z") + return t + }(), + Modified: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-07T16:17:48.131210Z") + return t + }(), + Name: "Demo Credential", + Description: "", + Organization: 1, + CredentialType: 1, + Inputs: map[string]interface{}{ + "username": "admin", + }, + }, + } + ) + + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + result, _, err := awx.CredentialService.ListCredentials(map[string]string{ + "name": "Default", + }) + + if err != nil { + t.Fatalf("ListCredentials err: %s", err) + } else { + checkAPICallResult(t, expectListCredentialsResponse, result) + t.Log("ListCredentials passed!") + } +} + +func TestCreateCredentials(t *testing.T) { + var ( + expectCreateCredentialsResponse = &Credential{ + ID: 1, + Type: "credential", + URL: "/api/v2/credentials/1/", + Related: &Related{ + ObjectRoles: "/api/v2/credentials/1/object_roles/", + AccessList: "/api/v2/credentials/1/access_list/", + CredentialType: "/api/v2/credential_types/1/", + ModifiedBy: "/api/v2/users/4/", + OwnerUsers: "/api/v2/credentials/1/owner_users/", + OwnerTeams: "/api/v2/credentials/1/owner_teams/", + Organization: "/api/v2/organizations/1/", + Copy: "/api/v2/credentials/1/copy/", + ActivityStream: "/api/v2/credentials/1/activity_stream/", + }, + SummaryFields: &Summary{ + Organization: &OrganizationSummary{ + ID: 1, + Name: "Default", + Description: "", + }, + Project: &Project{}, + ModifiedBy: &ByUserSummary{ + ID: 4, + Username: "admin", + FirstName: "", + LastName: "", + }, + ObjectRoles: &ObjectRoles{ + AdminRole: &ObjectRole{ + ID: 18, + Description: "Can manage all aspects of the credential", + Name: "Admin", + }, + UseRole: &ObjectRole{ + ID: 20, + Description: "Can use the credential in a job template", + Name: "Use", + }, + ReadRole: &ObjectRole{ + ID: 19, + Description: "May view settings for the credential", + Name: "Read", + }, + }, + UserCapabilities: &UserCapabilities{ + Edit: true, + Delete: true, + Copy: true, + }, + }, + Created: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-01T12:10:00.496424Z") + return t + }(), + Modified: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-07T16:17:48.131210Z") + return t + }(), + Name: "Demo Credential", + Description: "", + Organization: 1, + CredentialType: 1, + Inputs: map[string]interface{}{ + "username": "admin", + }, + } + ) + + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + result, err := awx.CredentialService.CreateCredential(map[string]interface{}{ + "name": "Demo Credential", + "organization": 1, + "credential_type": 1, + }, map[string]string{}) + if err != nil { + t.Fatalf("CreateCredentials err: %s", err) + } else { + checkAPICallResult(t, expectCreateCredentialsResponse, result) + t.Log("CreateCredentials passed!") + } +} + +func TestUpdateCredentials(t *testing.T) { + var ( + expectUpdateCredentialsResponse = &Credential{ + ID: 1, + Type: "credential", + URL: "/api/v2/credentials/1/", + Related: &Related{ + ObjectRoles: "/api/v2/credentials/1/object_roles/", + AccessList: "/api/v2/credentials/1/access_list/", + CredentialType: "/api/v2/credential_types/1/", + ModifiedBy: "/api/v2/users/4/", + OwnerUsers: "/api/v2/credentials/1/owner_users/", + OwnerTeams: "/api/v2/credentials/1/owner_teams/", + Organization: "/api/v2/organizations/1/", + Copy: "/api/v2/credentials/1/copy/", + ActivityStream: "/api/v2/credentials/1/activity_stream/", + }, + SummaryFields: &Summary{ + Organization: &OrganizationSummary{ + ID: 1, + Name: "Default", + Description: "", + }, + Project: &Project{}, + ModifiedBy: &ByUserSummary{ + ID: 4, + Username: "admin", + FirstName: "", + LastName: "", + }, + ObjectRoles: &ObjectRoles{ + AdminRole: &ObjectRole{ + ID: 18, + Description: "Can manage all aspects of the credential", + Name: "Admin", + }, + UseRole: &ObjectRole{ + ID: 20, + Description: "Can use the credential in a job template", + Name: "Use", + }, + ReadRole: &ObjectRole{ + ID: 19, + Description: "May view settings for the credential", + Name: "Read", + }, + }, + UserCapabilities: &UserCapabilities{ + Edit: true, + Delete: true, + Copy: true, + }, + }, + Created: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-01T12:10:00.496424Z") + return t + }(), + Modified: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-07T16:17:48.131210Z") + return t + }(), + Name: "Demo Credential", + Description: "Demo Credential", + Organization: 1, + CredentialType: 1, + Inputs: map[string]interface{}{ + "username": "admin", + }, + } + ) + + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + result, err := awx.CredentialService.UpdateCredential(1, map[string]interface{}{ + "name": "Demo Credential", + "description": "Demo credential", + "organization": 1, + "credential_type": 1, + "inputs": map[string]interface{}{ + "username": "admin", + }, + }, map[string]string{}) + if err != nil { + t.Fatalf("CreateCredentials err: %s", err) + } else { + checkAPICallResult(t, expectUpdateCredentialsResponse, result) + t.Log("CreateCredentials passed!") + } +} + +func TestDeleteCredentials(t *testing.T) { + var ( + expectDeleteCredentialsResponse = &Credential{} + ) + + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + result, err := awx.CredentialService.DeleteCredential(1) + + if err != nil { + t.Fatalf("DeleteCredentials err: %s", err) + } else { + checkAPICallResult(t, expectDeleteCredentialsResponse, result) + t.Log("DeleteCredentials passed!") + } +} +*/ diff --git a/examples/credentials.md b/examples/credentials.md new file mode 100644 index 0000000..e3da280 --- /dev/null +++ b/examples/credentials.md @@ -0,0 +1,97 @@ +# Credentiales API + +## Usage + +> List Credentiales + +```go +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, _, err := awx.CredentialService.ListCredentiales(map[string]string{ + "name": "Demo Credential", + }) + if err != nil { + log.Fatalf("List Credentiales err: %s", err) + } + + log.Println("List Credentiales: ", result) +} +``` + +> Create Credential + +```go +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +fun main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, err := awx.CredentialService.CreateCredential(map[string]interface{}{ + "name": "Demo Credential", + "organization": 1, + "credential_type": 1, + }, map[string]string{}) + + if err != nil { + log.Fatalf("Create Credentiales err: %s", err) + } + + log.Printf("Credential created. Credential ID: %d", result.Credential.ID") +} +``` + +> Update Credential + +```go +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +fun main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, err := awx.CredentialService.UpdateCredential(1, map[string]interface{}{ + "name": "Demo Credential", + "description": "Demo credential", + "organization": 1, + "credential_type": 1, + "inputs": map[string]interface{}{ + "username": "admin", + }, + }, nil) + + if err != nil { + log.Fatalf("Update Credentiales err: %s", err) + } + + log.Printf("Update result %v", result.Name) + +} +``` + +> Delete Credential + +```go +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, err := awx.CredentialService.DeleteCredential(5) + if err != nil { + log.Fatalf("Delete Credentiales err: %s", err) + } + + log.Println("Credential 5 Deleted") + +} +``` \ No newline at end of file diff --git a/examples/organizations.md b/examples/organizations.md new file mode 100644 index 0000000..3791f9c --- /dev/null +++ b/examples/organizations.md @@ -0,0 +1,133 @@ +# Organizations API + +## Usage + +> List Organizations + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, _, err := awx.OrganizationService.ListOrganizations(map[string]string{ + "name": "test-organization", + }) + if err != nil { + log.Fatalf("List Organizations err: %s", err) + } + + log.Println("List Organization: ", result) +} +``` + +> Create Organization + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, err := awx.OrganizationService.CreateOrganization(map[string]interface{}{ + "name": "test-organization", + "description": "test organization", + }, map[string]string{}) + if err != nil { + log.Fatalf("Create Organization err: %s", err) + } + + log.Printf("Organization created") +} +``` + +> Update Organization + +```go +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, err := awx.OrganizationService.UpdateOrganization(4, map[string]interface{}{ + "description": "Update test-organization", + }, map[string]string{}) + + if err != nil { + log.Fatalf("Update Organization err: %s", err) + } + + log.Printf("Update finised.") +} +``` + +> Delete Organization + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, err := awx.OrganizationService.DeleteOrganization(1) + + if err != nil { + log.Fatalf("Delete Organization err: %s", err) + } + + log.Printf("Organization Deleted") +} +``` + +> Grant Organization Role + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + err := awx.OrganizationService.GrantRole(1, 170) + + if err != nil { + log.Fatalf("Grant user role err: %s", err) + } + + log.Printf("User role granted") +} +``` + +> Revoke User Role + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + err := awx.OrganizationService.GrantRole(1, 170) + + if err != nil { + log.Fatalf("Revoke user role err: %s", err) + } + + log.Printf("User role revoked") +} +``` diff --git a/examples/teams.md b/examples/teams.md new file mode 100644 index 0000000..058988c --- /dev/null +++ b/examples/teams.md @@ -0,0 +1,136 @@ +# Teams API + +## Usage + +> List Teams + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, _, err := awx.TeamService.ListTeams(map[string]string{ + "name": "test-team", + }) + if err != nil { + log.Fatalf("List Teams err: %s", err) + } + + log.Println("List Team: ", result) +} +``` + +> Create Team + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, err := awx.TeamService.CreateTeam(map[string]interface{}{ + "name": "test-team", + "organization": 1, + }, map[string]string{}) + if err != nil { + log.Fatalf("Create Team err: %s", err) + } + + log.Printf("Team created") +} +``` + +> Update Team + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, err := awx.TeamService.UpdateTeam(4, map[string]interface{}{ + "name": "test-team", + "organization": 1, + "description": "Update test-team", + }, map[string]string{}) + + if err != nil { + log.Fatalf("Update Team err: %s", err) + } + + log.Printf("Update finised.") +} +``` + +> Delete Team + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + result, err := awx.TeamService.DeleteTeam(1) + + if err != nil { + log.Fatalf("Delete Team err: %s", err) + } + + log.Printf("Team Deleted") +} +``` + +> Grant Team Role + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + err := awx.TeamService.GrantRole(1, 170) + + if err != nil { + log.Fatalf("Grant user role err: %s", err) + } + + log.Printf("User role granted") +} +``` + +> Revoke User Role + +```go +package main +import ( + "log" + awxGo "github.com/Colstuwjx/awx-go" +) + +func main() { + awx := awxGo.NewAWX("http://awx.your_server_host.com", "your_awx_username", "your_awx_passwd", nil) + err := awx.TeamService.GrantRole(1, 170) + + if err != nil { + log.Fatalf("Revoke user role err: %s", err) + } + + log.Printf("User role revoked") +} +``` diff --git a/go.mod b/go.mod index 4daadf5..430c9bc 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,8 @@ module github.com/Colstuwjx/awx-go go 1.12 -require github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 +require ( + github.com/kylelemons/godebug v1.1.0 + github.com/stretchr/testify v1.5.1 + github.com/twinj/uuid v1.0.0 +) diff --git a/go.sum b/go.sum index 6827ab3..3bbf723 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,16 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk= +github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/groups.go b/groups.go index b337846..6501d2b 100644 --- a/groups.go +++ b/groups.go @@ -100,3 +100,28 @@ func (g *GroupService) DeleteGroup(id int) (*Group, error) { return result, nil } + +func (g *GroupService) AddChildGroup(groupID, childID int) (*Group, error) { + result := new(Group) + endpoint := fmt.Sprintf("/api/v2/groups/%d/children/", groupID) + payload := map[string]int{ + "id": childID, + } + + jsonPayload, err := json.Marshal(payload) + + if err != nil { + return nil, err + } + + resp, err := g.client.Requester.PostJSON(endpoint, bytes.NewReader(jsonPayload), result, nil) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} diff --git a/inventories_test.go b/inventories_test.go index 07bab1a..f96da20 100644 --- a/inventories_test.go +++ b/inventories_test.go @@ -33,58 +33,50 @@ func TestListInventories(t *testing.T) { Organization: "/api/v2/organizations/1/", }, SummaryFields: &Summary{ - Organization: &OrgnizationSummary{ + Organization: &OrganizationSummary{ ID: 1, Name: "Default", Description: "", }, - CreatedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ModifiedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ObjectRoles: &ObjectRoles{ - UseRole: &ApplyRole{ - ID: 23, - Description: "Can use the inventory in a job template", - Name: "Use", - }, - - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 21, Description: "Can manage all aspects of the inventory", Name: "Admin", }, - - AdhocRole: &ApplyRole{ + UpdateRole: &ObjectRole{ + ID: 24, + Description: "May update project or inventory or group using the configured source update system", + Name: "Update", + }, + AdhocRole: &ObjectRole{ ID: 20, Description: "May run ad hoc commands on an inventory", Name: "Ad Hoc", }, - - UpdateRole: &ApplyRole{ - ID: 24, - Description: "May update project or inventory or group using the configured source update system", - Name: "Update", + UseRole: &ObjectRole{ + ID: 23, + Description: "Can use the inventory in a job template", + Name: "Use", }, - - ReadRole: &ApplyRole{ + ReadRole: &ObjectRole{ ID: 22, Description: "May view settings for the inventory", Name: "Read", }, }, - UserCapabilities: &UserCapabilities{ Edit: true, Copy: true, @@ -164,58 +156,50 @@ func TestCreateInventory(t *testing.T) { Organization: "/api/v2/organizations/1/", }, SummaryFields: &Summary{ - Organization: &OrgnizationSummary{ + Organization: &OrganizationSummary{ ID: 1, Name: "Default", Description: "", }, - CreatedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ModifiedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ObjectRoles: &ObjectRoles{ - UseRole: &ApplyRole{ + UseRole: &ObjectRole{ ID: 80, Description: "Can use the inventory in a job template", Name: "Use", }, - - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 78, Description: "Can manage all aspects of the inventory", Name: "Admin", }, - - AdhocRole: &ApplyRole{ + AdhocRole: &ObjectRole{ ID: 77, Description: "May run ad hoc commands on an inventory", Name: "Ad Hoc", }, - - UpdateRole: &ApplyRole{ + UpdateRole: &ObjectRole{ ID: 81, Description: "May update project or inventory or group using the configured source update system", Name: "Update", }, - - ReadRole: &ApplyRole{ + ReadRole: &ObjectRole{ ID: 79, Description: "May view settings for the inventory", Name: "Read", }, }, - UserCapabilities: &UserCapabilities{ Edit: true, Copy: true, @@ -299,58 +283,50 @@ func TestUpdateInventory(t *testing.T) { Organization: "/api/v2/organizations/1/", }, SummaryFields: &Summary{ - Organization: &OrgnizationSummary{ + Organization: &OrganizationSummary{ ID: 1, Name: "Default", Description: "", }, - CreatedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ModifiedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ObjectRoles: &ObjectRoles{ - UseRole: &ApplyRole{ + UseRole: &ObjectRole{ ID: 80, Description: "Can use the inventory in a job template", Name: "Use", }, - - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 78, Description: "Can manage all aspects of the inventory", Name: "Admin", }, - - AdhocRole: &ApplyRole{ + AdhocRole: &ObjectRole{ ID: 77, Description: "May run ad hoc commands on an inventory", Name: "Ad Hoc", }, - - UpdateRole: &ApplyRole{ + UpdateRole: &ObjectRole{ ID: 81, Description: "May update project or inventory or group using the configured source update system", Name: "Update", }, - - ReadRole: &ApplyRole{ + ReadRole: &ObjectRole{ ID: 79, Description: "May view settings for the inventory", Name: "Read", }, }, - UserCapabilities: &UserCapabilities{ Edit: true, Copy: true, @@ -433,58 +409,50 @@ func TestGetInventory(t *testing.T) { Organization: "/api/v2/organizations/1/", }, SummaryFields: &Summary{ - Organization: &OrgnizationSummary{ + Organization: &OrganizationSummary{ ID: 1, Name: "Default", Description: "", }, - CreatedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ModifiedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ObjectRoles: &ObjectRoles{ - UseRole: &ApplyRole{ + UseRole: &ObjectRole{ ID: 80, Description: "Can use the inventory in a job template", Name: "Use", }, - - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 78, Description: "Can manage all aspects of the inventory", Name: "Admin", }, - - AdhocRole: &ApplyRole{ + AdhocRole: &ObjectRole{ ID: 77, Description: "May run ad hoc commands on an inventory", Name: "Ad Hoc", }, - - UpdateRole: &ApplyRole{ + UpdateRole: &ObjectRole{ ID: 81, Description: "May update project or inventory or group using the configured source update system", Name: "Update", }, - - ReadRole: &ApplyRole{ + ReadRole: &ObjectRole{ ID: 79, Description: "May view settings for the inventory", Name: "Read", }, }, - UserCapabilities: &UserCapabilities{ Edit: true, Copy: true, diff --git a/job.go b/job.go index c53b1d5..f6ee4ad 100644 --- a/job.go +++ b/job.go @@ -23,6 +23,15 @@ type JobService struct { client *Client } +type JobStdoutResponse struct { + Range struct { + Start int `json:"start"` + End int `json:"end"` + AbsoluteEnd int `json:"absolute_end"` + } `json:"range"` + Content string `json:"content"` +} + // HostSummariesResponse represents `JobHostSummaries` endpoint response. type HostSummariesResponse struct { Pagination @@ -129,3 +138,22 @@ func (j *JobService) GetJobEvents(id int, params map[string]string) ([]JobEvent, return result.Results, result, nil } + +func (j *JobService) GetJobStdOut(id int) (*JobStdoutResponse, error) { + result := new(JobStdoutResponse) + endpoint := fmt.Sprintf("/api/v2/jobs/%d/stdout/", id) + + resp, err := j.client.Requester.GetJSON(endpoint, result, map[string]string{ + "format": "json", + }) + + if err != nil { + return result, err + } + + if err := CheckResponse(resp); err != nil { + return result, err + } + + return result, nil +} diff --git a/job_template.go b/job_template.go index 4f62133..3f3a679 100644 --- a/job_template.go +++ b/job_template.go @@ -3,8 +3,9 @@ package awx import ( "bytes" "encoding/json" - "errors" "fmt" + + "github.com/twinj/uuid" ) // JobTemplateService implements awx job template apis. @@ -34,8 +35,8 @@ func (jt *JobTemplateService) ListJobTemplates(params map[string]string) ([]*Job return result.Results, result, nil } -// Launch lauchs a job with the job template. -func (jt *JobTemplateService) Launch(id int, data map[string]interface{}, params map[string]string) (*JobLaunch, error) { +// Launch launches a job with the job template. +func (jt *JobTemplateService) Launch(id int, data *JobLaunchOpts, params map[string]string) (*JobLaunch, error) { result := new(JobLaunch) endpoint := fmt.Sprintf("/api/v2/job_templates/%d/launch/", id) payload, err := json.Marshal(data) @@ -52,18 +53,42 @@ func (jt *JobTemplateService) Launch(id int, data map[string]interface{}, params return nil, err } - // in case invalid job id return - if result.Job == 0 { - return nil, errors.New("invalid job id 0") + return result, nil +} + +// CreateJobTemplateCallBack executes a PATCH HTTP Request to create the callback url and the generated host_config_key +func (jt *JobTemplateService) CreateJobTemplateCallBack(template *JobTemplate) (*JobTemplate, error) { + if template.ID == 0 { + return nil, fmt.Errorf("Job template ID must be passed") } - return result, nil + endpoint := fmt.Sprintf("/api/v2/job_templates/%d", template.ID) + template.AllowCallbacks = true + template.HostConfigKey = uuid.NewV4().String() + + jsonPayload, err := json.Marshal(template) + + if err != nil { + return nil, err + } + + resp, err := jt.client.Requester.PatchJSON(endpoint, bytes.NewReader(jsonPayload), template, map[string]string{}) + + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return template, nil } // CreateJobTemplate creates a job template func (jt *JobTemplateService) CreateJobTemplate(data map[string]interface{}, params map[string]string) (*JobTemplate, error) { result := new(JobTemplate) - mandatoryFields = []string{"name", "job_type", "inventory", "project"} + mandatoryFields = []string{"name", "job_type", "inventory", "project", "playbook"} validate, status := ValidateParams(data, mandatoryFields) if !status { err := fmt.Errorf("Mandatory input arguments are absent: %s", validate) @@ -82,6 +107,11 @@ func (jt *JobTemplateService) CreateJobTemplate(data map[string]interface{}, par if err := CheckResponse(resp); err != nil { return nil, err } + + if result.AllowCallbacks { + return jt.CreateJobTemplateCallBack(result) + } + return result, nil } @@ -120,3 +150,62 @@ func (jt *JobTemplateService) DeleteJobTemplate(id int) (*JobTemplate, error) { return result, nil } + +// GetJobTemplate gets a job template +func (jt *JobTemplateService) GetJobTemplate(id int) (*JobTemplate, error) { + result := new(JobTemplate) + endpoint := fmt.Sprintf("/api/v2/job_templates/%d", id) + + resp, err := jt.client.Requester.Get(endpoint, result, map[string]string{}) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} + +func (jt *JobTemplateService) AddJobTemplateCredential(jobTemplateID int, credID int) (*JobTemplate, error) { + result := new(JobTemplate) + endpoint := fmt.Sprintf("/api/v2/job_templates/%d/credentials/", jobTemplateID) + + payload := map[string]int{ + "id": credID, + } + + jsonPayload, err := json.Marshal(payload) + + if err != nil { + return nil, err + } + + resp, err := jt.client.Requester.PostJSON(endpoint, bytes.NewReader(jsonPayload), result, map[string]string{}) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} + +func (jt *JobTemplateService) GetSurveySpec(jobTemplate *JobTemplate) ([]byte, error) { + endpoint := jobTemplate.Related.SurveySpec + spec := make(map[string]interface{}) + resp, err := jt.client.Requester.Get(endpoint, spec, map[string]string{}) + + if err != nil { + return nil, err + } + + if err = CheckResponse(resp); err != nil { + return nil, err + } + + return json.Marshal(spec) +} diff --git a/job_template_test.go b/job_template_test.go index ea6512e..238d618 100644 --- a/job_template_test.go +++ b/job_template_test.go @@ -50,7 +50,6 @@ func TestListJobTemplates(t *testing.T) { OrganizationID: 1, Kind: "", }, - Project: &Project{ ID: 4, Name: "Demo Project", @@ -58,41 +57,35 @@ func TestListJobTemplates(t *testing.T) { Status: "never updated", ScmType: "git", }, - CreatedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ModifiedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ObjectRoles: &ObjectRoles{ - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 27, Description: "Can manage all aspects of the job template", Name: "Admin", }, - - ExecuteRole: &ApplyRole{ + ExecuteRole: &ObjectRole{ ID: 26, Description: "May run the job template", Name: "Execute", }, - - ReadRole: &ApplyRole{ + ReadRole: &ObjectRole{ ID: 25, Description: "May view settings for the job template", Name: "Read", }, }, - UserCapabilities: &UserCapabilities{ Edit: true, Start: true, @@ -100,14 +93,11 @@ func TestListJobTemplates(t *testing.T) { Schedule: true, Delete: true, }, - Labels: &Labels{ Count: 0, Results: []interface{}{}, }, - RecentJobs: []interface{}{}, - Credentials: []Credential{ { Description: "", @@ -211,14 +201,12 @@ func TestLauchJob(t *testing.T) { CreateSchedule: "/api/v2/jobs/499/create_schedule/", Relaunch: "/api/v2/jobs/499/relaunch/", }, - SummaryFields: &Summary{ JobTemplate: &JobTemplateSummary{ ID: 5, Name: "Demo Job Template", Description: "", }, - Inventory: &Inventory{ ID: 1, Name: "Demo Inventory", @@ -234,7 +222,6 @@ func TestLauchJob(t *testing.T) { OrganizationID: 1, Kind: "", }, - Credential: &Credential{ Description: "", CredentialTypeID: 1, @@ -242,14 +229,12 @@ func TestLauchJob(t *testing.T) { Kind: "ssh", Name: "Demo Credential", }, - UnifiedJobTemplate: &UnifiedJobTemplate{ ID: 5, Name: "Demo Job Template", Description: "", UnifiedJobType: "job", }, - Project: &Project{ ID: 4, Name: "Demo Project", @@ -257,31 +242,26 @@ func TestLauchJob(t *testing.T) { Status: "successful", ScmType: "git", }, - CreatedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - ModifiedBy: &ByUserSummary{ ID: 1, Username: "admin", FirstName: "", LastName: "", }, - UserCapabilities: &UserCapabilities{ Start: true, Delete: true, }, - Labels: &Labels{ Count: 0, Results: []interface{}{}, }, - ExtraCredentials: []interface{}{}, Credentials: []Credential{ { @@ -356,8 +336,8 @@ func TestLauchJob(t *testing.T) { ) awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) - result, err := awx.JobTemplateService.Launch(testJobTemplateID, map[string]interface{}{ - "inventory": testInventoryID, + result, err := awx.JobTemplateService.Launch(testJobTemplateID, &JobLaunchOpts{ + Inventory: testInventoryID, }, map[string]string{}) if err != nil { @@ -432,17 +412,17 @@ func TestCreateJobTemplate(t *testing.T) { LastName: "", }, ObjectRoles: &ObjectRoles{ - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 28, Description: "Can manage all aspects of the job template", Name: "Admin", }, - ExecuteRole: &ApplyRole{ + ExecuteRole: &ObjectRole{ ID: 27, Description: "May run the job template", Name: "Execute", }, - ReadRole: &ApplyRole{ + ReadRole: &ObjectRole{ ID: 26, Description: "May view settings for the job template", Name: "Read", @@ -595,17 +575,17 @@ func TestUpdateJobTemplate(t *testing.T) { LastName: "", }, ObjectRoles: &ObjectRoles{ - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 28, Description: "Can manage all aspects of the job template", Name: "Admin", }, - ExecuteRole: &ApplyRole{ + ExecuteRole: &ObjectRole{ ID: 27, Description: "May run the job template", Name: "Execute", }, - ReadRole: &ApplyRole{ + ReadRole: &ObjectRole{ ID: 26, Description: "May view settings for the job template", Name: "Read", diff --git a/organizations.go b/organizations.go new file mode 100644 index 0000000..b8381bd --- /dev/null +++ b/organizations.go @@ -0,0 +1,103 @@ +package awx + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// OrganizationService implements awx Organizations apis. +type OrganizationService struct { + client *Client +} + +// ListOrganizationsResponse represents `ListOrganizations` endpoint response. +type ListOrganizationsResponse struct { + Pagination + Results []*Organization `json:"results"` +} + +// ListOrganizations shows list of awx Organizations. +func (t *OrganizationService) ListOrganizations(params map[string]string) ([]*Organization, *ListOrganizationsResponse, error) { + result := new(ListOrganizationsResponse) + endpoint := "/api/v2/organizations/" + resp, err := t.client.Requester.GetJSON(endpoint, result, params) + if err != nil { + return nil, result, err + } + + if err := CheckResponse(resp); err != nil { + return nil, result, err + } + + return result.Results, result, nil +} + +// CreateOrganization creates an awx Organization. +func (t *OrganizationService) CreateOrganization(data map[string]interface{}, params map[string]string) (*Organization, error) { + mandatoryFields = []string{"name"} + validate, status := ValidateParams(data, mandatoryFields) + + if !status { + err := fmt.Errorf("Mandatory input arguments are absent: %s", validate) + return nil, err + } + + result := new(Organization) + endpoint := "/api/v2/organizations/" + payload, err := json.Marshal(data) + if err != nil { + return nil, err + } + + // Add check if Organization exists and return proper error + + resp, err := t.client.Requester.PostJSON(endpoint, bytes.NewReader(payload), result, params) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} + +// UpdateOrganization update an awx user. +func (t *OrganizationService) UpdateOrganization(id int, data map[string]interface{}, params map[string]string) (*Organization, error) { + result := new(Organization) + endpoint := fmt.Sprintf("/api/v2/organizations/%d", id) + payload, err := json.Marshal(data) + if err != nil { + return nil, err + } + + resp, err := t.client.Requester.PatchJSON(endpoint, bytes.NewReader(payload), result, params) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} + +// DeleteOrganization delete an awx Organization. +func (t *OrganizationService) DeleteOrganization(id int) (*Organization, error) { + result := new(Organization) + endpoint := fmt.Sprintf("/api/v2/organizations/%d", id) + + resp, err := t.client.Requester.Delete(endpoint, result, nil) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} diff --git a/organizations_test.go b/organizations_test.go new file mode 100644 index 0000000..16633ec --- /dev/null +++ b/organizations_test.go @@ -0,0 +1,64 @@ +// +build integration + +package awx + +import ( + "fmt" + "math/rand" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/suite" +) + +type OrganizationsTestSuite struct { + suite.Suite + client *AWX + service *OrganizationService +} + +func TestProviders(t *testing.T) { + suite.Run(t, &OrganizationsTestSuite{}) +} + +func (suite *OrganizationsTestSuite) SetupAllSuite() { + rand.Seed(time.Now().UnixNano()) +} + +func (suite *OrganizationsTestSuite) SetupTest() { + suite.client = NewAWX("http://localhost", "admin", "password", &http.Client{}) + suite.service = suite.client.OrganizationService + suite.T().Logf("AWX: %+v", suite.client) +} + +func (suite *OrganizationsTestSuite) TestCreateDeleteOrganization() { + name := fmt.Sprintf("Testing-%v", rand.Int()) + org, err := suite.service.CreateOrganization(map[string]interface{}{ + "name": name, + "description": "A Test organization", + }, map[string]string{}) + suite.Nil(err) + suite.NotNil(org.ID) // Make sure that the org was created by making sure an ID was set + suite.Equal(org.Name, name) + org, err = suite.client.OrganizationService.DeleteOrganization(org.ID) + suite.Nil(err) +} + +func (suite *OrganizationsTestSuite) TestUpdateOrganization() { + description := fmt.Sprintf("This is a test %v", time.Now().String()) + org, err := suite.service.UpdateOrganization(1, map[string]interface{}{ + "description": description, + }, map[string]string{}) + suite.Nil(err) + suite.Equal(org.Description, description) +} + +func (suite *OrganizationsTestSuite) TestListOrganizations() { + org, _, err := suite.service.ListOrganizations(map[string]string{}) + suite.Nil(err) + suite.NotEmpty(org) + suite.Equal(org[0].Type, "organization") + suite.Equal(org[0].Name, "Default") + suite.T().Logf("%+v", org) +} diff --git a/project_updates_test.go b/project_updates_test.go index 3f10bb3..31bc70e 100644 --- a/project_updates_test.go +++ b/project_updates_test.go @@ -52,13 +52,11 @@ func TestProjectUpdateGet(t *testing.T) { ID: 1, Name: "tower", }, - JobTemplate: &JobTemplateSummary{ ID: 8, Name: "Hello-world", Description: "", }, - Inventory: &Inventory{ ID: 1, Name: "Demo Inventory", @@ -74,7 +72,6 @@ func TestProjectUpdateGet(t *testing.T) { OrganizationID: 1, Kind: "", }, - ProjectUpdate: &ProjectUpdate{ ID: 303, Name: "DeployBook", @@ -82,14 +79,12 @@ func TestProjectUpdateGet(t *testing.T) { Status: "successful", Failed: false, }, - UnifiedJobTemplate: &UnifiedJobTemplate{ ID: 8, Description: "", Name: "Hello-world", UnifiedJobType: "job", }, - Project: &Project{ ID: 6, Name: "DeployBook", diff --git a/projects_test.go b/projects_test.go index da7076e..bb9bb2f 100644 --- a/projects_test.go +++ b/projects_test.go @@ -22,7 +22,7 @@ func TestListProjects(t *testing.T) { Organization: "/api/v2/organizations/1/", }, SummaryFields: &Summary{ - Organization: &OrgnizationSummary{ + Organization: &OrganizationSummary{ ID: 1, Name: "Default", Description: "", @@ -40,31 +40,27 @@ func TestListProjects(t *testing.T) { LastName: "admin", }, ObjectRoles: &ObjectRoles{ - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 14, Description: "Can manage all aspects of the project", Name: "Admin", }, - - UseRole: &ApplyRole{ - ID: 16, - Description: "Can manage all aspects of the project", - Name: "Use", - }, - - UpdateRole: &ApplyRole{ + UpdateRole: &ObjectRole{ ID: 17, Description: "May update project or inventory or group using the configured source update system", Name: "Update", }, - - ReadRole: &ApplyRole{ + UseRole: &ObjectRole{ + ID: 16, + Description: "Can manage all aspects of the project", + Name: "Use", + }, + ReadRole: &ObjectRole{ ID: 15, Description: "May view settings for the project", Name: "Read", }, }, - UserCapabilities: &UserCapabilities{ Edit: true, Start: true, @@ -127,7 +123,7 @@ func TestCreateProject(t *testing.T) { Organization: "/api/v2/organizations/1/", }, SummaryFields: &Summary{ - Organization: &OrgnizationSummary{ + Organization: &OrganizationSummary{ ID: 1, Name: "Default", Description: "", @@ -145,31 +141,27 @@ func TestCreateProject(t *testing.T) { LastName: "admin", }, ObjectRoles: &ObjectRoles{ - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 14, Description: "Can manage all aspects of the project", Name: "Admin", }, - - UseRole: &ApplyRole{ - ID: 16, - Description: "Can manage all aspects of the project", - Name: "Use", - }, - - UpdateRole: &ApplyRole{ + UpdateRole: &ObjectRole{ ID: 17, Description: "May update project or inventory or group using the configured source update system", Name: "Update", }, - - ReadRole: &ApplyRole{ + UseRole: &ObjectRole{ + ID: 16, + Description: "Can manage all aspects of the project", + Name: "Use", + }, + ReadRole: &ObjectRole{ ID: 15, Description: "May view settings for the project", Name: "Read", }, }, - UserCapabilities: &UserCapabilities{ Edit: true, Start: true, @@ -234,7 +226,7 @@ func TestUpdateProject(t *testing.T) { Organization: "/api/v2/organizations/1/", }, SummaryFields: &Summary{ - Organization: &OrgnizationSummary{ + Organization: &OrganizationSummary{ ID: 1, Name: "Default", Description: "", @@ -252,31 +244,27 @@ func TestUpdateProject(t *testing.T) { LastName: "admin", }, ObjectRoles: &ObjectRoles{ - AdminRole: &ApplyRole{ + AdminRole: &ObjectRole{ ID: 14, Description: "Can manage all aspects of the project", Name: "Admin", }, - - UseRole: &ApplyRole{ - ID: 16, - Description: "Can manage all aspects of the project", - Name: "Use", - }, - - UpdateRole: &ApplyRole{ + UpdateRole: &ObjectRole{ ID: 17, Description: "May update project or inventory or group using the configured source update system", Name: "Update", }, - - ReadRole: &ApplyRole{ + UseRole: &ObjectRole{ + ID: 16, + Description: "Can manage all aspects of the project", + Name: "Use", + }, + ReadRole: &ObjectRole{ ID: 15, Description: "May view settings for the project", Name: "Read", }, }, - UserCapabilities: &UserCapabilities{ Edit: true, Start: true, diff --git a/teams.go b/teams.go new file mode 100644 index 0000000..d8deeaa --- /dev/null +++ b/teams.go @@ -0,0 +1,152 @@ +package awx + +import ( + "bytes" + "encoding/json" + "fmt" +) + +// TeamService implements awx Teams apis. +type TeamService struct { + client *Client +} + +// ListTeamsResponse represents `ListTeams` endpoint response. +type ListTeamsResponse struct { + Pagination + Results []*Team `json:"results"` +} + +// ListTeams shows list of awx Teams. +func (t *TeamService) ListTeams(params map[string]string) ([]*Team, *ListTeamsResponse, error) { + result := new(ListTeamsResponse) + endpoint := "/api/v2/teams/" + resp, err := t.client.Requester.GetJSON(endpoint, result, params) + if err != nil { + return nil, result, err + } + + if err := CheckResponse(resp); err != nil { + return nil, result, err + } + + return result.Results, result, nil +} + +// CreateTeam creates an awx Team. +func (t *TeamService) CreateTeam(data map[string]interface{}, params map[string]string) (*Team, error) { + mandatoryFields = []string{"name", "organization"} + validate, status := ValidateParams(data, mandatoryFields) + + if !status { + err := fmt.Errorf("Mandatory input arguments are absent: %s", validate) + return nil, err + } + + result := new(Team) + endpoint := "/api/v2/teams/" + payload, err := json.Marshal(data) + if err != nil { + return nil, err + } + + // Add check if Team exists and return proper error + + resp, err := t.client.Requester.PostJSON(endpoint, bytes.NewReader(payload), result, params) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} + +// UpdateTeam update an awx user. +func (t *TeamService) UpdateTeam(id int, data map[string]interface{}, params map[string]string) (*Team, error) { + result := new(Team) + endpoint := fmt.Sprintf("/api/v2/teams/%d", id) + payload, err := json.Marshal(data) + if err != nil { + return nil, err + } + + resp, err := t.client.Requester.PatchJSON(endpoint, bytes.NewReader(payload), result, params) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} + +// DeleteTeam delete an awx Team. +func (t *TeamService) DeleteTeam(id int) (*Team, error) { + result := new(Team) + endpoint := fmt.Sprintf("/api/v2/teams/%d", id) + + resp, err := t.client.Requester.Delete(endpoint, result, nil) + if err != nil { + return nil, err + } + + if err := CheckResponse(resp); err != nil { + return nil, err + } + + return result, nil +} + +// GrantRole grant the provided role to the AWX Team +func (t *TeamService) GrantRole(id int, roleID int) error { + result := new(Team) + endpoint := fmt.Sprintf("/api/v2/teams/%d/roles/", id) + data := map[string]interface{}{ + "id": roleID, + } + payload, err := json.Marshal(data) + if err != nil { + return err + } + + resp, err := t.client.Requester.PostJSON(endpoint, bytes.NewReader(payload), result, nil) + if err != nil { + return err + } + + if err := CheckResponse(resp); err != nil { + return err + } + + return nil +} + +// RevokeRole revoke the provided role to the AWX Team +func (t *TeamService) RevokeRole(id int, roleID int) error { + result := new(Team) + endpoint := fmt.Sprintf("/api/v2/teams/%d/roles/", id) + data := map[string]interface{}{ + "id": roleID, + "disassociate": "true", + } + payload, err := json.Marshal(data) + if err != nil { + return err + } + + resp, err := t.client.Requester.PostJSON(endpoint, bytes.NewReader(payload), result, nil) + if err != nil { + return err + } + + if err := CheckResponse(resp); err != nil { + return err + } + + return nil +} diff --git a/teams_test.go b/teams_test.go new file mode 100644 index 0000000..2f79d26 --- /dev/null +++ b/teams_test.go @@ -0,0 +1,307 @@ +package awx + +// TODO Add mock data and then enable test +/* +import ( + "testing" + "time" +) + +func TestListTeams(t *testing.T) { + var ( + expectListTeamsResponse = []*Team{ + { + ID: 1, + Type: "team", + URL: "/api/v2/teams/1/", + Related: &Related{ + CreatedBy: "/api/v2/users/4/", + ModifiedBy: "/api/v2/users/4/", + Projects: "/api/v2/teams/1/projects/", + Users: "/api/v2/teams/1/users/", + Credentials: "/api/v2/teams/1/credentials/", + Roles: "/api/v2/teams/1/roles/", + ObjectRoles: "/api/v2/teams/1/object_roles/", + ActivityStream: "/api/v2/teams/1/activity_stream/", + AccessList: "/api/v2/teams/1/access_list/", + Organization: "/api/v2/organizations/1/", + }, + SummaryFields: &SummaryFields{ + Organization: &OrganizationSummary{ + ID: 1, + Name: "Default", + Description: "", + }, + CreatedBy: &ByUserSummary{ + ID: 4, + Username: "admin", + FirstName: "", + LastName: "", + }, + ModifiedBy: &ByUserSummary{ + ID: 4, + Username: "admin", + FirstName: "", + LastName: "", + }, + UserCapabilities: &UserCapabilities{ + Edit: true, + Delete: true, + }, + ObjectRoles: &ObjectRoles{ + AdminRole: &ObjectRole{ + ID: 30, + Description: "Can manage all aspects of the team", + Name: "Admin", + }, + MemberRole: &ObjectRole{ + ID: 29, + Description: "User is a member of the team", + Name: "Member", + }, + ReadRole: &ObjectRole{ + ID: 31, + Description: "May view settings for the team", + Name: "Read", + }, + }, + }, + Created: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-01T13:33:30.692904Z") + return t + }(), + Modified: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-01T13:33:30.692904Z") + return t + }(), + Name: "test-team", + Organization: 1, + Description: "", + }, + } + ) + + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + result, _, err := awx.TeamService.ListTeams(map[string]string{ + "name": "test-team", + }) + + if err != nil { + t.Fatalf("ListTeams err: %s", err) + } else { + checkAPICallResult(t, expectListTeamsResponse, result) + t.Log("ListTeams passed!") + } +} + +func TestCreateTeam(t *testing.T) { + var ( + expectCreateTeamResponse = &Team{ + ID: 1, + Type: "team", + URL: "/api/v2/teams/1/", + Related: &Related{ + CreatedBy: "/api/v2/users/4/", + ModifiedBy: "/api/v2/users/4/", + Users: "/api/v2/teams/1/users/", + Roles: "/api/v2/teams/1/roles/", + ObjectRoles: "/api/v2/teams/1/object_roles/", + Credentials: "/api/v2/teams/1/credentials/", + Projects: "/api/v2/teams/1/projects/", + ActivityStream: "/api/v2/teams/1/activity_stream/", + AccessList: "/api/v2/teams/1/access_list/", + Organization: "/api/v2/organizations/1/", + }, + SummaryFields: &SummaryFields{ + UserCapabilities: &UserCapabilities{ + Edit: true, + Delete: true, + }, + Organization: &OrganizationSummary{ + ID: 1, + Name: "Default", + Description: "", + }, + ObjectRoles: &ObjectRoles{ + AdminRole: &ObjectRole{ + ID: 30, + Description: "Can manage all aspects of the team", + Name: "Admin", + }, + MemberRole: &ObjectRole{ + ID: 29, + Description: "User is a member of the team", + Name: "Member", + }, + ReadRole: &ObjectRole{ + ID: 31, + Description: "May view settings for the team", + Name: "Read", + }, + }, + CreatedBy: &ByUserSummary{ + ID: 4, + Username: "admin", + FirstName: "", + LastName: "", + }, + ModifiedBy: &ByUserSummary{ + ID: 4, + Username: "admin", + FirstName: "", + LastName: "", + }, + }, + Created: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-01T13:33:30.692904Z") + return t + }(), + Modified: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-01T13:33:30.692904Z") + return t + }(), + Name: "test-team", + Organization: 1, + Description: "", + } + ) + + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + result, err := awx.TeamService.CreateTeam(map[string]interface{}{ + "name": "test-team", + "organization": 1, + }, map[string]string{}) + if err != nil { + t.Fatalf("CreateTeam err: %s", err) + } else { + checkAPICallResult(t, expectCreateTeamResponse, result) + t.Log("CreateTeam passed!") + } +} + +func TestUpdateTeam(t *testing.T) { + var ( + expectUpdateTeamResponse = &Team{ + ID: 1, + Type: "team", + URL: "/api/v2/teams/1/", + Related: &Related{ + CreatedBy: "/api/v2/users/4/", + ModifiedBy: "/api/v2/users/4/", + Projects: "/api/v2/teams/1/projects/", + Users: "/api/v2/teams/1/users/", + Credentials: "/api/v2/teams/1/credentials/", + Roles: "/api/v2/teams/1/roles/", + ObjectRoles: "/api/v2/teams/1/object_roles/", + ActivityStream: "/api/v2/teams/1/activity_stream/", + AccessList: "/api/v2/teams/1/access_list/", + Organization: "/api/v2/organizations/1/", + }, + SummaryFields: &SummaryFields{ + Organization: &OrganizationSummary{ + ID: 1, + Name: "Default", + Description: "", + }, + CreatedBy: &ByUserSummary{ + ID: 4, + Username: "admin", + FirstName: "", + LastName: "", + }, + ModifiedBy: &ByUserSummary{ + ID: 4, + Username: "admin", + FirstName: "", + LastName: "", + }, + ObjectRoles: &ObjectRoles{ + AdminRole: &ObjectRole{ + ID: 30, + Description: "Can manage all aspects of the team", + Name: "Admin", + }, + MemberRole: &ObjectRole{ + ID: 29, + Description: "User is a member of the team", + Name: "Member", + }, + ReadRole: &ObjectRole{ + ID: 31, + Description: "May view settings for the team", + Name: "Read", + }, + }, + UserCapabilities: &UserCapabilities{ + Edit: true, + Delete: true, + }, + }, + Created: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-01T13:33:30.692904Z") + return t + }(), + Modified: func() time.Time { + t, _ := time.Parse(time.RFC3339, "2018-12-01T13:33:30.692904Z") + return t + }(), + Name: "test-team", + Organization: 1, + Description: "Update test-team", + } + ) + + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + result, err := awx.TeamService.UpdateTeam(4, map[string]interface{}{ + "name": "test-team", + "organization": 1, + "description": "Update test-team", + }, map[string]string{}) + + if err != nil { + t.Fatalf("CreateTeam err: %s", err) + } else { + checkAPICallResult(t, expectUpdateTeamResponse, result) + t.Log("CreateTeam passed!") + } +} +func TestDeleteTeam(t *testing.T) { + var ( + expectDeleteTeamResponse = &Team{} + ) + + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + result, err := awx.TeamService.DeleteTeam(1) + + if err != nil { + t.Fatalf("DeleteTeam err: %s", err) + } else { + checkAPICallResult(t, expectDeleteTeamResponse, result) + t.Log("DeleteTeam passed!") + } +} + +func TestTeamGrantRole(t *testing.T) { + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + err := awx.TeamService.GrantRole(4, 170) + + if err != nil { + t.Fatalf("TestTeamGrantRole err: %s", err) + } else { + checkAPICallResult(t, nil, nil) + t.Log("TestTeamGrantRole passed!") + } +} + +func TestTeamRevokeRole(t *testing.T) { + awx := NewAWX(testAwxHost, testAwxUserName, testAwxPasswd, nil) + err := awx.TeamService.RevokeRole(1, 170) + + if err != nil { + t.Fatalf("TestTeamRevokeRole err: %s", err) + } else { + checkAPICallResult(t, nil, nil) + t.Log("TestTeamRevokeRole passed!") + } +} +*/ diff --git a/types.go b/types.go index f5f9bbe..ee04622 100644 --- a/types.go +++ b/types.go @@ -80,10 +80,11 @@ type Related struct { AdHocCommandEvents string `json:"ad_hoc_command_events"` Children string `json:"children"` AnsibleFacts string `json:"ansible_facts"` + Callback string `json:"callback"` } -// OrgnizationSummary represents the awx api orgnization summary fields. -type OrgnizationSummary struct { +// OrganizationSummary represents the awx api organization summary fields. +type OrganizationSummary struct { ID int `json:"id"` Name string `json:"name"` Description string `json:"description"` @@ -111,23 +112,30 @@ type InstanceGroupSummary struct { Description string `json:"description"` } -// ApplyRole represents the awx api apply role. -type ApplyRole struct { +// ObjectRoles represents the awx api object roles. +type ObjectRoles struct { + UseRole *ObjectRole `json:"use_role"` + AdminRole *ObjectRole `json:"admin_role"` + AdhocRole *ObjectRole `json:"adhoc_role"` + UpdateRole *ObjectRole `json:"update_role"` + ReadRole *ObjectRole `json:"read_role"` + ExecuteRole *ObjectRole `json:"execute_role"` + MemberRole *ObjectRole `json:"member_role"` + NotificationAdminRole *ObjectRole `json:"notification_admin_role"` + WorkflowAdminRole *ObjectRole `json:"workflow_admin_role"` + CredentialAdminRole *ObjectRole `json:"credential_admin_role"` + JobTemplateAdminRole *ObjectRole `json:"job_template_admin_role"` + ProjectAdminRole *ObjectRole `json:"project_admin_role"` + AuditorRole *ObjectRole `json:"auditor_role"` + InventoryAdminRole *ObjectRole `json:"inventory_admin_role"` +} + +type ObjectRole struct { ID int `json:"id"` Name string `json:"name"` Description string `json:"description"` } -// ObjectRoles represents the awx api object roles. -type ObjectRoles struct { - UseRole *ApplyRole `json:"use_role"` - AdminRole *ApplyRole `json:"admin_role"` - AdhocRole *ApplyRole `json:"adhoc_role"` - UpdateRole *ApplyRole `json:"update_role"` - ReadRole *ApplyRole `json:"read_role"` - ExecuteRole *ApplyRole `json:"execute_role"` -} - // UserCapabilities represents the awx api user capabilities. type UserCapabilities struct { Edit bool `json:"edit"` @@ -147,7 +155,7 @@ type Labels struct { // Summary represents the awx api summary fields. type Summary struct { InstanceGroup *InstanceGroupSummary `json:"instance_group"` - Organization *OrgnizationSummary `json:"organization"` + Organization *OrganizationSummary `json:"organization"` CreatedBy *ByUserSummary `json:"created_by"` ModifiedBy *ByUserSummary `json:"modified_by"` ObjectRoles *ObjectRoles `json:"object_roles"` @@ -336,6 +344,7 @@ type JobTemplate struct { CustomVirtualenv interface{} `json:"custom_virtualenv"` Credential int `json:"credential"` VaultCredential interface{} `json:"vault_credential"` + AllowCallbacks bool `json:"allow_callbacks"` } // JobLaunch represents the awx api job launch. @@ -399,6 +408,19 @@ type JobLaunch struct { VaultCredential interface{} `json:"vault_credential"` } +type JobLaunchOpts struct { + ExtraVars map[string]interface{} `json:"extra_vars,omitempty"` + Inventory int `json:"inventory,omitempty"` + Limit string `json:"limit,omitempty"` + JobTags string `json:"job_tags,omitempty"` + SkipTags string `json:"skip_tags,omitempty"` + JobType string `json:"job_type,omitempty"` + Verbosity int `json:"verbosity,omitempty"` + DiffMode interface{} `json:"diff_mode,omitempty"` + Credentials []int `json:"credentials,omitempty"` + CredentialPasswords []string `json:"credential_passwords,omitempty"` +} + // Job represents the awx api job. type Job struct { ID int `json:"id"` @@ -659,3 +681,65 @@ type Host struct { InsightsSystemID interface{} `json:"insights_system_id"` AnsibleFactsModified interface{} `json:"ansible_facts_modified"` } + +type Organization struct { + ID int `json:"id"` + Type string `json:"type"` + URL string `json:"url"` + Related Related `json:"related"` + SummaryFields SummaryFields `json:"summary_fields"` + Created time.Time `json:"created"` + Modified time.Time `json:"modified"` + Name string `json:"name"` + Description string `json:"description"` + CustomVirtualEnv interface{} `json:"custom_virtualenv"` +} + +type Team struct { + ID int `json:"id"` + Type string `json:"type"` + URL string `json:"url"` + Related Related `json:"related"` + SummaryFields SummaryFields `json:"summary_fields"` + Created time.Time `json:"created"` + Modified time.Time `json:"modified"` + Name string `json:"name"` + Description string `json:"description"` + Organization int `json:"organization"` +} + +type RelatedFieldCounts struct { + JobTemplates int `json:"job_templates"` + Users int `json:"users"` + Teams int `json:"teams"` + Admins int `json:"admins"` + Inventories int `json:"inventories"` + Projects int `json:"projects"` +} + +type SummaryFields struct { + CreatedBy ByUserSummary `json:"created_by"` + ModifiedBy ByUserSummary `json:"modified_by"` + ObjectRoles ObjectRoles `json:"object_roles"` + UserCapabilities UserCapabilities `json:"user_capabilities"` + RelatedFieldCounts RelatedFieldCounts `json:"related_field_counts"` +} + +type SurveySpec struct { + Name string `json:"name"` + Description string `json:"description"` + Spec []Spec `json:"spec"` +} + +type Spec struct { + QuestionName string `json:"question_name"` + QuestionDescription string `json:"question_description"` + Required bool `json:"required"` + Type string `json:"type"` + Variable string `json:"variable"` + Min int `json:"min"` + Max int `json:"max"` + Default string `json:"default"` + Choices string `json:"choices"` + NewQuestion bool `json:"new_question"` +} diff --git a/users.go b/users.go index e06a94f..b81c982 100644 --- a/users.go +++ b/users.go @@ -101,3 +101,51 @@ func (u *UserService) DeleteUser(id int) (*User, error) { return result, nil } + +func (u *UserService) RevokeRole(id, roleID string) error { + result := new(User) + endpoint := fmt.Sprintf("/api/v2/users/%s", id) + jsonPayload := map[string]string{ + "disassociate": roleID, + } + + j, err := json.Marshal(jsonPayload) + + if err != nil { + return err + } + + resp, err := u.client.Requester.PostJSON(endpoint, bytes.NewReader(j), result, jsonPayload) + if err != nil { + return err + } + + if err := CheckResponse(resp); err != nil { + return err + } + return nil +} + +func (u *UserService) GrantRole(id, roleID string) error { + result := new(User) + endpoint := fmt.Sprintf("/api/v2/users/%s", id) + jsonPayload := map[string]string{ + "id": roleID, + } + + j, err := json.Marshal(jsonPayload) + + if err != nil { + return err + } + + resp, err := u.client.Requester.PostJSON(endpoint, bytes.NewReader(j), result, jsonPayload) + if err != nil { + return err + } + + if err := CheckResponse(resp); err != nil { + return err + } + return nil +}