From 25609a441d3637f2ed461e8e8126b607c8d1b02e Mon Sep 17 00:00:00 2001 From: Andy Lo-A-Foe Date: Tue, 19 Sep 2023 11:00:10 +0200 Subject: [PATCH] Remove HAS and TPNS --- README.md | 1 - has/README.md | 55 ------- has/client.go | 270 ---------------------------------- has/client_test.go | 198 ------------------------- has/errors.go | 14 -- has/images_service.go | 35 ----- has/images_service_test.go | 59 -------- has/resources_service.go | 241 ------------------------------ has/resources_service_test.go | 258 -------------------------------- has/sessions_service.go | 124 ---------------- has/sessions_service_test.go | 129 ---------------- internal/version.go | 2 +- tpns/README.md | 38 ----- tpns/client.go | 234 ----------------------------- tpns/client_test.go | 136 ----------------- tpns/coding.go | 9 -- tpns/errors.go | 10 -- tpns/issue_response.go | 18 --- tpns/messages_service.go | 36 ----- tpns/parse.go | 36 ----- tpns/parse_test.go | 28 ---- 21 files changed, 1 insertion(+), 1930 deletions(-) delete mode 100644 has/README.md delete mode 100644 has/client.go delete mode 100644 has/client_test.go delete mode 100644 has/errors.go delete mode 100644 has/images_service.go delete mode 100644 has/images_service_test.go delete mode 100644 has/resources_service.go delete mode 100644 has/resources_service_test.go delete mode 100644 has/sessions_service.go delete mode 100644 has/sessions_service_test.go delete mode 100644 tpns/README.md delete mode 100644 tpns/client.go delete mode 100644 tpns/client_test.go delete mode 100644 tpns/coding.go delete mode 100644 tpns/errors.go delete mode 100644 tpns/issue_response.go delete mode 100644 tpns/messages_service.go delete mode 100644 tpns/parse.go delete mode 100644 tpns/parse_test.go diff --git a/README.md b/README.md index a0fb9715..1ffaa932 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,6 @@ The current implement covers only a subset of HSDP APIs. Basically, we implement - [x] DICOM Store - [x] Config management - [x] Notification service -- [x] Hosted Application Streaming (HAS) management - [x] Service Discovery - [x] Console settings - [ ] Metrics Alerts diff --git a/has/README.md b/has/README.md deleted file mode 100644 index bf8ebfc6..00000000 --- a/has/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Using the HAS API client -Hosted Application Streaming is a HSDP service which provides application deployment in the cloud or on premise with streaming output to your browser. This allows one to quickly scale up and consume workstation class hardware on any browser enabled system. - -# Provisioning HAS resources - -```golang -import ( - "fmt" - - "github.com/philips-software/go-hsdp-api/has" - "github.com/philips-software/go-hsdp-api/iam" -) - -func main() { - iamClient, err := iam.NewClient(nil, &iam.Config{ - OAuth2ClientID: "YourClientID", - OAuth2Secret: "YourClientSecret", - Region: "eu-west", - Environment: "client-test", - }) - if err != nil { - fmt.Printf("Error creating client: %v\n", err) - } - err = iamClient.Login("yourlogin", "yourpassword") - if err != nil { - fmt.Printf("Error: %v\n", err) - return - } - client, err := has.NewClient(iamClient, &has.Config{ - HASURL: "https://has-client-test.eu-west.philips-healthsuite.com", - OrgID: "your-org-uuid-here", - }) - if err != nil { - fmt.Printf("Error creating HAS client: %v\n", err) - return - } - images, resp, err := client.Images.GetImages() - fmt.Printf("%v %v %v", images, resp, err) - - res, resp, err := client.Resources.CreateResource(has.Resource{ - ImageID: "ami-0fc5fakeimageidhere", - ResourceType: "g3s.xlarge", - Region: "eu-west-1", - Count: 1, - ClusterTag: "andytest", - EBS: has.EBS{ - DeleteOnTermination: true, - VolumeSize: 50, - VolumeType: "standard", - }, - }) - - fmt.Printf("%v %v %v", res, resp, err) -} -``` diff --git a/has/client.go b/has/client.go deleted file mode 100644 index e2614588..00000000 --- a/has/client.go +++ /dev/null @@ -1,270 +0,0 @@ -// Package has provides support for HSDP Appstreaming service -package has - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "sort" - "strings" - - "github.com/philips-software/go-hsdp-api/internal" - - "github.com/google/go-querystring/query" - "github.com/philips-software/go-hsdp-api/iam" -) - -const ( - userAgent = "go-hsdp-api/has/" + internal.LibraryVersion -) - -// OptionFunc is the function signature function for options -type OptionFunc func(*http.Request) error - -// Config contains the configuration of a client -type Config struct { - HASURL string - OrgID string - Debug bool - DebugLog io.Writer -} - -// A Client manages communication with HSDP IAM API -type Client struct { - // HTTP client used to communicate with the API. - iamClient *iam.Client - - config *Config - - baseHASURL *url.URL - - // User agent used when communicating with the HSDP IAM API. - UserAgent string - - Resources *ResourcesService - Sessions *SessionsService - Images *ImagesService -} - -// NewClient returns a new HSDP HAS API client. If a nil httpClient is -// provided, http.DefaultClient will be used. A configured IAM client must be provided -// as well -func NewClient(iamClient *iam.Client, config *Config) (*Client, error) { - return newClient(iamClient, config) -} - -func newClient(iamClient *iam.Client, config *Config) (*Client, error) { - c := &Client{iamClient: iamClient, config: config, UserAgent: userAgent} - if err := c.SetBaseHASURL(c.config.HASURL); err != nil { - return nil, err - } - if config.OrgID == "" || !iamClient.HasPermissions(config.OrgID, - "HAS_SESSION.ALL", "HAS_RESOURCE.ALL") { - return nil, ErrMissingHASPermissions - } - - c.Resources = &ResourcesService{client: c, orgID: config.OrgID} - c.Sessions = &SessionsService{client: c, orgID: config.OrgID} - c.Images = &ImagesService{client: c, orgID: config.OrgID} - return c, nil -} - -// Close releases allocated resources of clients -func (c *Client) Close() { -} - -// SetBaseHASURL sets the base URL for API requests to a custom endpoint. urlStr -// should always be specified with a trailing slash. -func (c *Client) SetBaseHASURL(urlStr string) error { - if urlStr == "" { - return ErrBaseHASCannotBeEmpty - } - // Make sure the given URL end with a slash - if !strings.HasSuffix(urlStr, "/") { - urlStr += "/" - } - - var err error - c.baseHASURL, err = url.Parse(urlStr) - return err -} - -// newHASRequest creates an new HAS API request. A relative URL path can be provided in -// urlStr, in which case it is resolved relative to the base URL of the Client. -// Relative URL paths should always be specified without a preceding slash. If -// specified, the value pointed to by body is JSON encoded and included as the -// request body. -func (c *Client) newHASRequest(method, path string, opt interface{}, options []OptionFunc) (*http.Request, error) { - u := *c.baseHASURL - // Set the encoded opaque data - u.Opaque = c.baseHASURL.Path + path - - if opt != nil { - q, err := query.Values(opt) - if err != nil { - return nil, err - } - u.RawQuery = q.Encode() - } - - req := &http.Request{ - Method: method, - URL: &u, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: make(http.Header), - Host: u.Host, - } - - for _, fn := range options { - if fn == nil { - continue - } - - if err := fn(req); err != nil { - return nil, err - } - } - - if method == "POST" || method == "PUT" { - bodyBytes, err := json.Marshal(opt) - if err != nil { - return nil, err - } - bodyReader := bytes.NewReader(bodyBytes) - - u.RawQuery = "" - req.Body = io.NopCloser(bodyReader) - req.ContentLength = int64(bodyReader.Len()) - req.Header.Set("Content-Type", "application/json") - } - token, err := c.iamClient.Token() - if err != nil { - return nil, err - } - req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", "Bearer "+token) - - if c.UserAgent != "" { - req.Header.Set("User-Agent", c.UserAgent) - } - return req, nil -} - -// Response is a HSDP IAM API response. This wraps the standard http.Response -// returned from HSDP IAM and provides convenient access to things like errors -type Response struct { - *http.Response -} - -func (r *Response) StatusCode() int { - if r.Response != nil { - return r.Response.StatusCode - } - return 0 -} - -// newResponse creates a new Response for the provided http.Response. -func newResponse(r *http.Response) *Response { - response := &Response{Response: r} - return response -} - -// do executes a http request. If v implements the io.Writer -// interface, the raw response body will be written to v, without attempting to -// first decode it. -func (c *Client) do(req *http.Request, v interface{}) (*Response, error) { - resp, err := c.iamClient.HttpClient().Do(req) - if resp != nil { - defer func() { - _ = resp.Body.Close() - }() - } - if err != nil { - return nil, err - } - response := newResponse(resp) - - err = checkResponse(resp) - if err != nil { - // even though there was an error, we still return the response - // in case the caller wants to inspect it further - return response, err - } - - if v != nil { - if w, ok := v.(io.Writer); ok { - _, err = io.Copy(w, resp.Body) - } else { - err = json.NewDecoder(resp.Body).Decode(v) - } - } - - return response, err -} - -// ErrorResponse represents an IAM errors response -// containing a code and a human readable message -type ErrorResponse struct { - Response *http.Response `json:"-"` - Code string `json:"responseCode"` - Message string `json:"responseMessage"` -} - -func (e *ErrorResponse) Error() string { - path, _ := url.QueryUnescape(e.Response.Request.URL.Opaque) - u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, path) - return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message) -} - -func checkResponse(r *http.Response) error { - switch r.StatusCode { - case 200, 201, 202, 204, 304: - return nil - } - - errorResponse := &ErrorResponse{Response: r} - data, err := io.ReadAll(r.Body) - if err == nil && data != nil { - var raw interface{} - if err := json.Unmarshal(data, &raw); err != nil { - errorResponse.Message = "failed to parse unknown error format" - } - - errorResponse.Message = parseError(raw) - } - - return errorResponse -} - -func parseError(raw interface{}) string { - switch raw := raw.(type) { - case string: - return raw - - case []interface{}: - var errs []string - for _, v := range raw { - errs = append(errs, parseError(v)) - } - return fmt.Sprintf("[%s]", strings.Join(errs, ", ")) - - case map[string]interface{}: - var errs []string - for k, v := range raw { - errs = append(errs, fmt.Sprintf("{%s: %s}", k, parseError(v))) - } - sort.Strings(errs) - return strings.Join(errs, ", ") - case float64: - return fmt.Sprintf("%d", int64(raw)) - case int64: - return fmt.Sprintf("%d", raw) - default: - return fmt.Sprintf("failed to parse unexpected error type: %T", raw) - } -} diff --git a/has/client_test.go b/has/client_test.go deleted file mode 100644 index 018b2cf2..00000000 --- a/has/client_test.go +++ /dev/null @@ -1,198 +0,0 @@ -package has_test - -import ( - "io" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/philips-software/go-hsdp-api/has" - - "github.com/philips-software/go-hsdp-api/iam" - "github.com/stretchr/testify/assert" -) - -var ( - muxIAM *http.ServeMux - serverIAM *httptest.Server - muxIDM *http.ServeMux - serverIDM *httptest.Server - muxHAS *http.ServeMux - serverHAS *httptest.Server - - iamClient *iam.Client - hasClient *has.Client - hasOrgID = "48a0183d-a588-41c2-9979-737d15e9e860" - userUUID = "e7fecbb2-af8c-47c9-a662-5b046e048bc5" -) - -func setup(t *testing.T) func() { - muxIAM = http.NewServeMux() - serverIAM = httptest.NewServer(muxIAM) - muxIDM = http.NewServeMux() - serverIDM = httptest.NewServer(muxIDM) - muxHAS = http.NewServeMux() - serverHAS = httptest.NewServer(muxHAS) - - var err error - iamClient, err = iam.NewClient(nil, &iam.Config{ - OAuth2ClientID: "TestClient", - OAuth2Secret: "Secret", - SharedKey: "SharedKey", - SecretKey: "SecretKey", - IAMURL: serverIAM.URL, - IDMURL: serverIDM.URL, - }) - if err != nil { - t.Fatalf("Failed to create iamCleitn: %v", err) - } - token := "44d20214-7879-4e35-923d-f9d4e01c9746" - - muxIAM.HandleFunc("/authorize/oauth2/token", func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - t.Errorf("Expected ‘POST’ request") - w.WriteHeader(http.StatusBadRequest) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "scope": "mail tdr.contract tdr.dataitem", - "access_token": "`+token+`", - "refresh_token": "31f1a449-ef8e-4bfc-a227-4f2353fde547", - "expires_in": 1799, - "token_type": "Bearer" -}`) - }) - muxIAM.HandleFunc("/authorize/oauth2/introspect", func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - t.Errorf("Expected ‘POST’ request") - w.WriteHeader(http.StatusBadRequest) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "active": true, - "scope": "auth_iam_organization auth_iam_introspect mail openid profile cn", - "username": "ronswanson", - "exp": 1592073485, - "sub": "`+userUUID+`", - "iss": "https://iam-client-test.us-east.philips-healthsuite.com/oauth2/access_token", - "organizations": { - "managingOrganization": "`+hasOrgID+`", - "organizationList": [ - { - "organizationId": "`+hasOrgID+`", - "permissions": [ - "ANALYZE_WORKFLOW.ALL", - "ANALYZE_DATAPROC.ALL", - "USER.READ", - "GROUP.WRITE", - "DEVICE.READ", - "CLIENT.SCOPES", - "AMS_ACCESS.ALL", - "HAS_RESOURCE.ALL", - "HAS_SESSION.ALL" - ], - "effectivePermissions": [ - "ANALYZE_WORKFLOW.ALL", - "ANALYZE_DATAPROC.ALL", - "USER.READ", - "GROUP.WRITE", - "DEVICE.READ", - "CLIENT.SCOPES", - "AMS_ACCESS.ALL", - "HAS_RESOURCE.ALL", - "HAS_SESSION.ALL" - ], - "organizationName": "PawneeOrg", - "groups": [ - "AdminGroup" - ], - "roles": [ - "ADMIN", - "HASROLE" - ] - } - ] - }, - "client_id": "testclientid", - "token_type": "Bearer", - "identity_type": "user" -}`) - }) - - // Login immediately so we can create tdrClient - err = iamClient.Login("username", "password") - assert.Nil(t, err) - - hasClient, err = has.NewClient(iamClient, &has.Config{ - HASURL: serverHAS.URL, - OrgID: hasOrgID, - }) - assert.Nilf(t, err, "failed to create hasClient: %v", err) - - return func() { - serverIAM.Close() - serverIDM.Close() - serverHAS.Close() - } -} - -func TestLogin(t *testing.T) { - teardown := setup(t) - defer teardown() - - token := "44d20214-7879-4e35-923d-f9d4e01c9746" - - err := iamClient.Login("username", "password") - if err != nil { - t.Fatal(err) - } - accessToken, err := iamClient.Token() - if !assert.Nil(t, err) { - return - } - assert.Equal(t, token, accessToken) -} - -func TestDebug(t *testing.T) { - teardown := setup(t) - defer teardown() - - orgID := "48a0183d-a588-41c2-9979-737d15e9e860" - - tmpfile, err := os.CreateTemp("", "example") - if err != nil { - t.Fatalf("Error: %v", err) - } - - hasClient, err = has.NewClient(iamClient, &has.Config{ - HASURL: serverHAS.URL, - Debug: true, - OrgID: orgID, - DebugLog: tmpfile, - }) - if !assert.Nil(t, err) { - return - } - - defer hasClient.Close() - defer os.Remove(tmpfile.Name()) // clean up - - err = iamClient.Login("username", "password") - if !assert.Nil(t, err) { - return - } - - _, _, _ = hasClient.Resources.GetResources(&has.ResourceOptions{}) - _, _, _ = hasClient.Resources.CreateResource(has.Resource{}) - _, _, _ = hasClient.Resources.DeleteResources(&has.ResourceOptions{}) - - fi, err := tmpfile.Stat() - assert.Nil(t, err) - assert.NotEqual(t, 0, fi.Size(), "Expected something to be written to DebugLog") -} diff --git a/has/errors.go b/has/errors.go deleted file mode 100644 index 73be549d..00000000 --- a/has/errors.go +++ /dev/null @@ -1,14 +0,0 @@ -package has - -import ( - "errors" -) - -// Errors -var ( - ErrBaseHASCannotBeEmpty = errors.New("base HAS URL cannot be empty") - ErrMissingHASPermissions = errors.New("missing HAS permissions. Need 'HAS_RESOURCE.ALL' and 'HAS_SESSION.ALL'") - ErrEmptyResult = errors.New("empty result") - ErrCouldNoReadResourceAfterCreate = errors.New("could not read resource after create") - ErrEmptyResults = errors.New("empty results") -) diff --git a/has/images_service.go b/has/images_service.go deleted file mode 100644 index 6a6767c7..00000000 --- a/has/images_service.go +++ /dev/null @@ -1,35 +0,0 @@ -package has - -type ImagesService struct { - orgID string - client *Client -} - -type Image struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Regions []string `json:"regions"` -} - -type imageResult struct { - Images []Image `json:"images"` -} - -// GetImages retrieves images in HAS -func (c *ImagesService) GetImages(options ...OptionFunc) (*[]Image, *Response, error) { - req, err := c.client.newHASRequest("GET", "has/image", nil, options) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var ir imageResult - - resp, err := c.client.do(req, &ir) - if err != nil { - return nil, resp, err - } - return &ir.Images, resp, nil -} diff --git a/has/images_service_test.go b/has/images_service_test.go deleted file mode 100644 index 2bf6c082..00000000 --- a/has/images_service_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package has_test - -import ( - "io" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGetImages(t *testing.T) { - teardown := setup(t) - defer teardown() - - muxHAS.HandleFunc("/has/image", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - switch r.Method { - case "GET": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "images": [ - { - "id": "has-image-j4aakl0ie7b3", - "name": "BaseImage2k12r2", - "description": "Base image for Windows Server 2012 R2", - "regions": [ - "us-east-1", - "us-east-2", - "us-west-2", - "eu-west-1" - ] - }, - { - "id": "has-image-98fur6fbsiff", - "name": "BaseImageWS2k16", - "description": "Base Image for windows server 2016", - "regions": [ - "us-east-1" - ] - } - ] -}`) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - }) - - images, resp, err := hasClient.Images.GetImages() - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, images) { - return - } - assert.Equal(t, 2, len(*images)) -} diff --git a/has/resources_service.go b/has/resources_service.go deleted file mode 100644 index 8a821d1a..00000000 --- a/has/resources_service.go +++ /dev/null @@ -1,241 +0,0 @@ -package has - -// The HAS Resource API provides access to a windows application virtualization service. -// This API allows a user to manage a pool of storage and computing resources used for session based access. -// The pool is managed by a scheduler, which will take care of day-to-day resource scheduling, and which can -// be influenced or overruled through parameters in this interface. -// -// Ref: https://www.hsdp.io/documentation/hosted-application-streaming/ - -import ( - "net/http" -) - -// ResourcesService provides operations on HAS resources -type ResourcesService struct { - orgID string - client *Client -} - -// Constants -const ( - APIVersion = "1" -) - -// Resource -type Resource struct { - // the id of the resource. - ID string `json:"id,omitempty"` - // id for virtual machine image. This typically lives as an AMI and is defined elsewhere. Example: "ami-454654654" - ImageID string `json:"imageId" validate:"required"` - // Reference to resource type of cloud provider. Default will be a machine type configured for a VM image. Example: "g3.4xlarge" - ResourceType string `json:"resourceType" validate:"required"` - // The region where the resource will be hosted. See the providers documentation for available regions. - Region string `json:"region" validate:"required"` - // Number of resources requested. The maximum amount to request resources in one api call is 10. Defaults to 1. - Count int `json:"count" validate:"min=1,max=10,required"` - // Allows to perform filters and queries on a cluster of resources. - ClusterTag string `json:"clusterTag" validate:"required"` - EBS EBS `json:"ebs" validate:"required"` - // Unique identifier to a resource (i.e. server/storage instance) - ResourceID string `json:"resourceId,omitempty"` - // The id of the organization that this resource belongs to. - OrganizationID string `json:"organizationId,omitempty"` - // The current sessionId of the session which has claimed the resource, will be not present when not claimed. - SessionID string `json:"sessionId,omitempty"` - // State of AWS resource. - State string `json:"state,omitempty" enum:"PENDING|RUNNING|SHUTTING-DOWN|TERMINATED|REBOOTING|STOPPING|STOPPED|UNKNOWN"` - // The DNS name of the resource - DNS string `json:"dns,omitempty"` - // When a resource is disabled it means that the resource is deleted and wont be used for sessions. - // It will only be used as reference for historical session. - Disabled bool `json:"disabled,omitempty"` -} - -// EBS options provided to AWS -type EBS struct { - DeleteOnTermination bool `json:"DeleteOnTermination"` - Encrypted bool `json:"Encrypted"` - Iops int `json:"Iops,omitempty"` - KmsKeyID string `json:"KmsKeyId,omitempty"` - SnapshotID string `json:"SnapshotId,omitempty"` - VolumeSize int `json:"VolumeSize" validate:"min=20,required"` - VolumeType string `json:"VolumeType" validate:"required" enum:"standard|io1|gp2|sc1|st1"` -} - -type resourceResponse struct { - Resources []Resource `json:"resources"` - Error string `json:"error,omitempty"` - ErrorDescription string `json:"error_description,omitempty"` -} - -// Result of a resource action API call -type Result struct { - ResourceID string `json:"resourceId"` - Action string `json:"action"` - ResultCode int `json:"resultCode"` - ResultMessage string `json:"resultMessage"` -} - -// ResourcesReports represents a list of Result values -type ResourcesReport struct { - Results []Result `json:"results"` -} - -// CreateResource creates a new resource in HAS -// A server will be prepared immediately. -// That is, an EBS backed AMI will be created from provided reference and started. -// The Post operation allows a user to add new resources to the pool or cluster. -// This is an operational action, and requires elevated permissions to operate. -// This endpoint requires HAS_RESOURCE.ALL permission. -func (c *ResourcesService) CreateResource(resource Resource) (*[]Resource, *Response, error) { - req, err := c.client.newHASRequest("POST", "resource", &resource, nil) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var cr resourceResponse - resp, err := c.client.do(req, &cr) - if err != nil { - return nil, resp, err - } - if resp.StatusCode() != http.StatusOK { - return nil, resp, err - } - return &cr.Resources, resp, nil -} - -type ResourceOptions struct { - ClusterTag *string `url:"clusterTag,omitempty"` - ImageID *string `url:"imageId,omitempty"` - ResourceType *string `url:"resourceType,omitempty"` - ResourceID *string `url:"resourceId,omitempty"` - SessionID *string `url:"sessionId,omitempty"` - State *string `url:"state,omitempty"` - Region *string `url:"region,omitempty"` - Force *bool `url:"force,omitempty"` -} - -// GetResources retrieves resources in HAS -// Get overview of all current resource claims. -// All fields are case-sensitive. -// This endpoint requires HAS_RESOURCE.ALL permission. -func (c *ResourcesService) GetResources(opt *ResourceOptions, options ...OptionFunc) (*[]Resource, *Response, error) { - req, err := c.client.newHASRequest("GET", "resource", opt, options) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var gr resourceResponse - - resp, err := c.client.do(req, &gr) - if err != nil { - return nil, resp, err - } - return &gr.Resources, resp, nil -} - -// DeleteResources deletes resources in HAS -// Delete multiple resources that are reserved. -// Resources that are claimed by a user can only -// be deleted by adding the force option. -// This endpoint requires HAS_RESOURCE.ALL permission. -func (c *ResourcesService) DeleteResources(opt *ResourceOptions, options ...OptionFunc) (*ResourcesReport, *Response, error) { - req, err := c.client.newHASRequest("DELETE", "resource", opt, options) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var dr ResourcesReport - - resp, err := c.client.do(req, &dr) - if err != nil { - return nil, resp, err - } - return &dr, resp, nil -} - -// StartResource starts a resource in HAS -// Start a resource to make sure it is in Running state. -// This endpoint requires HAS_RESOURCE.ALL permission. -func (c *ResourcesService) StartResource(resources []string, options ...OptionFunc) (*ResourcesReport, *Response, error) { - return c.startStopResource("start", resources, options...) -} - -// StopResource stops a resource in HAS -// Stop a resource to make sure it is in Stopped state. -// This endpoint requires HAS_RESOURCE.ALL permission. -func (c *ResourcesService) StopResource(resources []string, options ...OptionFunc) (*ResourcesReport, *Response, error) { - return c.startStopResource("stop", resources, options...) -} - -func (c *ResourcesService) startStopResource(action string, resources []string, options ...OptionFunc) (*ResourcesReport, *Response, error) { - var resourceList = struct { - ResourceIDs []string `json:"resourceIds"` - }{ - ResourceIDs: resources, - } - - req, err := c.client.newHASRequest("POST", "resource/"+action, &resourceList, options) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var gr ResourcesReport - - resp, err := c.client.do(req, &gr) - if err != nil { - return nil, resp, err - } - return &gr, resp, nil -} - -// GetResource retrieves a resource in HAS -// Get overview of the requested resource. -// This endpoint requires HAS_RESOURCE.ALL permission. -func (c *ResourcesService) GetResource(resourceID string, options ...OptionFunc) (*Resource, *Response, error) { - req, err := c.client.newHASRequest("GET", "resource/"+resourceID, nil, options) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var gr Resource - - resp, err := c.client.do(req, &gr) - if err != nil { - return nil, resp, err - } - return &gr, resp, nil -} - -// DeleteResource deletes resources in HAS -// Delete a resource that is reserved. -// Resources that are claimed by a user can only be -// deleted by adding the force option. -// This endpoint requires HAS_RESOURCE.ALL permission. -func (c *ResourcesService) DeleteResource(resourceID string, options ...OptionFunc) (*ResourcesReport, *Response, error) { - req, err := c.client.newHASRequest("DELETE", "resource/"+resourceID, nil, options) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var dr ResourcesReport - - resp, err := c.client.do(req, &dr) - if err != nil { - return nil, resp, err - } - return &dr, resp, nil -} diff --git a/has/resources_service_test.go b/has/resources_service_test.go deleted file mode 100644 index d95b936d..00000000 --- a/has/resources_service_test.go +++ /dev/null @@ -1,258 +0,0 @@ -package has_test - -import ( - "io" - "net/http" - "testing" - - "github.com/philips-software/go-hsdp-api/has" - - "github.com/stretchr/testify/assert" -) - -func TestResourcesCRUD(t *testing.T) { - teardown := setup(t) - defer teardown() - - resourceID := "i-0f23dfed98e55913c" - muxHAS.HandleFunc("/resource", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - switch r.Method { - case "POST": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "resources": [ - { - "id": "cke8gjrz10gew1305fzatnxm7", - "resourceId": "i-08a214f0f844df43a", - "organizationId": "`+hasOrgID+`", - "imageId": "has-image-j4iikl0ie7b3", - "resourceType": "g3s.xlarge", - "clusterTag": "created-with-hs", - "sessionId": "", - "dns": "10.0.214.59", - "state": "PENDING", - "disabled": false, - "region": "eu-west-1" - } - ] -}`) - case "DELETE": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "results": [ - { - "resourceId": "i-08a214f0f844df43a", - "action": "DELETE", - "resultCode": 200, - "resultMessage": "Success" - } - ] -}`) - case "GET": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "resources": [ - { - "id": "cke8gjrz10gew1305fzatnxm7", - "resourceId": "i-08a214f0f844df43a", - "organizationId": "`+hasOrgID+`", - "imageId": "has-image-j4iikl0ie7b3", - "resourceType": "g3s.xlarge", - "clusterTag": "created-with-hs", - "sessionId": "", - "dns": "10.0.214.59", - "state": "RUNNING", - "disabled": false, - "region": "eu-west-1" - } - ] -}`) - } - }) - muxHAS.HandleFunc("/resource/stop", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - switch r.Method { - case "POST": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "results": [ - { - "resourceId": "i-0f23dfed98e55913c", - "action": "STOP", - "resultCode": 200, - "resultMessage": "Success" - } - ] -}`) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - }) - - muxHAS.HandleFunc("/resource/start", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - switch r.Method { - case "POST": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "results": [ - { - "resourceId": "i-0f23dfed98e55913c", - "action": "START", - "resultCode": 200, - "resultMessage": "Success" - } - ] -}`) - default: - w.WriteHeader(http.StatusMethodNotAllowed) - } - }) - - muxHAS.HandleFunc("/resource/"+resourceID, func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - switch r.Method { - case "PUT": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "id": "cke8gjrz10gew1305fzatnxm7", - "resourceId": "i-08a214f0f844df43a", - "organizationId": "`+hasOrgID+`", - "imageId": "has-image-j4iikl0ie7b3", - "resourceType": "g3s.xlarge", - "clusterTag": "created-with-hs", - "sessionId": "", - "dns": "10.0.214.59", - "state": "RUNNING", - "disabled": false, - "region": "eu-west-1" - }`) - case "GET": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "id": "cke8gjrz10gew1305fzatnxm7", - "resourceId": "i-08a214f0f844df43a", - "organizationId": "`+hasOrgID+`", - "imageId": "has-image-j4iikl0ie7b3", - "resourceType": "g3s.xlarge", - "clusterTag": "created-with-hs", - "sessionId": "", - "dns": "10.0.214.59", - "state": "RUNNING", - "disabled": false, - "region": "eu-west-1" - }`) - case "DELETE": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "results": [ - { - "resourceId": "i-08a214f0f844df43a", - "action": "DELETE", - "resultCode": 200, - "resultMessage": "Success" - } - ] -}`) - } - }) - - r := has.Resource{ - ImageID: "has-image-xxx", - ResourceType: "g3s.xlarge", - Count: 1, - ClusterTag: "created-with-hs", - EBS: has.EBS{ - DeleteOnTermination: true, - Encrypted: true, - VolumeSize: 50, - VolumeType: "standard", - }, - } - - resources, resp, err := hasClient.Resources.CreateResource(r) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, resources) { - return - } - if !assert.Equal(t, 1, len(*resources)) { - return - } - assert.Equal(t, "has-image-j4iikl0ie7b3", (*resources)[0].ImageID) - - resource, resp, err := hasClient.Resources.GetResource(resourceID) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, resource) { - return - } - assert.Equal(t, "has-image-j4iikl0ie7b3", resource.ImageID) - - report, resp, err := hasClient.Resources.StopResource([]string{resourceID}) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, report) { - return - } - report, resp, err = hasClient.Resources.StartResource([]string{resourceID}) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, report) { - return - } - - resources, resp, err = hasClient.Resources.GetResources(&has.ResourceOptions{ - ResourceID: &resourceID, - }) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, resources) { - return - } - - resourceResponse, resp, err := hasClient.Resources.DeleteResource(resourceID) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, resourceResponse) { - return - } - - resourceResponse, resp, err = hasClient.Resources.DeleteResources(&has.ResourceOptions{ - ImageID: &resourceID, - }) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, resourceResponse) { - return - } -} diff --git a/has/sessions_service.go b/has/sessions_service.go deleted file mode 100644 index 00c37caa..00000000 --- a/has/sessions_service.go +++ /dev/null @@ -1,124 +0,0 @@ -package has - -import ( - "fmt" - "net/http" -) - -// SessionsService provides operations on HAS sessions -type SessionsService struct { - orgID string - client *Client -} - -// Session describes a HAS session -type Session struct { - // reference to session (to query specific status) - SessionID string `json:"sessionId,omitempty"` - //Full URL to resource (empty if not yet available) - SessionURL string `json:"sessionUrl,omitempty"` - // The session type - SessionType string `json:"sessionType,omitempty" enum:"DEV"` - // Enumerated status of resource claim - State string `json:"state,omitempty" enum:"PENDING|AVAILABLE|TIMEDOUT|INUSE"` - // The id of the resource that is used for this session - ResourceID string `json:"resourceId,omitempty"` - // The region where the resource was provisioned. - Region string `json:"region,omitempty"` - // The id of the user that has claimed this session. - UserID string `json:"userId,omitempty"` - // The image id of the session - ImageID string `json:"imageId,omitempty"` - // The cluster tag to target the specific cluster - ClusterTag string `json:"clusterTag,omitempty"` - // The remote IP of the instance - RemoteIP string `json:"remoteIp,omitempty"` - // The access token for the instance - AccessToken string `json:"accessToken,omitempty"` -} - -// Sessions contains a list of Session values -type Sessions struct { - Sessions []Session `json:"sessions"` - ResponseError -} - -// ResponseError describes the response fields in case of error -type ResponseError struct { - Error string `json:"error,omitempty"` - ErrorDescription string `json:"error_description,omitempty"` -} - -// CreateSession creates a new user session in HAS -func (c *SessionsService) CreateSession(userID string, session Session) (*Sessions, *Response, error) { - req, err := c.client.newHASRequest("POST", "user/"+userID+"/session", &session, nil) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var sr Sessions - resp, err := c.client.do(req, &sr) - if err != nil { - return nil, resp, err - } - return &sr, resp, nil -} - -// SessionOptions describes options (query) parameters which -// can be used on some API endpoints -type SessionOptions struct { - ResourceID *string `url:"resourceId,omitempty"` -} - -// GetSession gets a user session in HAS -func (c *SessionsService) GetSession(userID string, opt *SessionOptions) (*Sessions, *Response, error) { - req, err := c.client.newHASRequest("GET", "user/"+userID+"/session", &opt, nil) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var sr Sessions - resp, err := c.client.do(req, &sr) - if err != nil { - return nil, resp, err - } - return &sr, resp, nil -} - -// GetSessions gets all sessions in HAS -func (c *SessionsService) GetSessions() (*Sessions, *Response, error) { - req, err := c.client.newHASRequest("GET", "session", nil, nil) - if err != nil { - return nil, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var sr Sessions - resp, err := c.client.do(req, &sr) - if err != nil { - return nil, resp, err - } - return &sr, resp, nil -} - -// DeleteSession deletes a user session in HAS -func (c *SessionsService) DeleteSession(userID string) (bool, *Response, error) { - req, err := c.client.newHASRequest("DELETE", "user/"+userID+"/session", nil, nil) - if err != nil { - return false, nil, err - } - req.Header.Set("organizationId", c.orgID) - req.Header.Set("Api-Version", APIVersion) - - var sr Sessions - resp, _ := c.client.do(req, &sr) - if resp == nil || resp.StatusCode() != http.StatusNoContent { - return false, nil, fmt.Errorf("DeleteSession: %w", ErrEmptyResults) - } - return true, resp, nil -} diff --git a/has/sessions_service_test.go b/has/sessions_service_test.go deleted file mode 100644 index 5b12f971..00000000 --- a/has/sessions_service_test.go +++ /dev/null @@ -1,129 +0,0 @@ -package has_test - -import ( - "io" - "net/http" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/philips-software/go-hsdp-api/has" -) - -func TestSessionsCRUD(t *testing.T) { - teardown := setup(t) - defer teardown() - - sessionID := "cke8qn6hs0gjb1305088jt9w6" - imageID := "has-image-j4jjkl0ie7b3" - resourceID := "i-0f23dfed98e55913c" - - muxHAS.HandleFunc("/session", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - switch r.Method { - case "GET": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "sessions": [ - { - "sessionId": "`+sessionID+`", - "sessionUrl": "https://some.url/session?token=xxx#console", - "state": "AVAILABLE", - "region": "eu-west-1", - "resourceId": "`+resourceID+`", - "userId": "`+userUUID+`", - "sessionType": "USER" - } - ] -}`) - } - }) - - muxHAS.HandleFunc("/user/"+userUUID+"/session", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - switch r.Method { - case "POST": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "sessions": [ - { - "sessionId": "`+sessionID+`", - "sessionUrl": "", - "state": "PENDING", - "region": "eu-west-1", - "resourceId": "`+resourceID+`", - "userId": "`+userUUID+`", - "sessionType": "USER" - } - ] -}`) - case "DELETE": - w.WriteHeader(http.StatusNoContent) - case "GET": - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "sessions": [ - { - "sessionId": "`+sessionID+`", - "sessionUrl": "https://some.url/session?token=xxx#console", - "state": "AVAILABLE", - "region": "eu-west-1", - "resourceId": "`+resourceID+`", - "userId": "`+userUUID+`", - "sessionType": "USER" - } - ] -}`) - } - }) - - sessions, resp, err := hasClient.Sessions.CreateSession(userUUID, has.Session{ - Region: "eu-west-1", - ImageID: imageID, - ClusterTag: "created-with-hs", - }) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, sessions) { - return - } - - sessions, resp, err = hasClient.Sessions.GetSessions() - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, sessions) { - return - } - - sessions, resp, err = hasClient.Sessions.GetSession(userUUID, &has.SessionOptions{ - ResourceID: &resourceID, - }) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.NotNil(t, sessions) { - return - } - - ok, resp, err := hasClient.Sessions.DeleteSession(userUUID) - if !assert.Nil(t, err) { - return - } - if !assert.NotNil(t, resp) { - return - } - if !assert.True(t, ok) { - return - } -} diff --git a/internal/version.go b/internal/version.go index 83af5747..a3f33ba2 100644 --- a/internal/version.go +++ b/internal/version.go @@ -1,5 +1,5 @@ package internal const ( - LibraryVersion = "0.82.5" + LibraryVersion = "0.83.0" ) diff --git a/tpns/README.md b/tpns/README.md deleted file mode 100644 index 820c1fb9..00000000 --- a/tpns/README.md +++ /dev/null @@ -1,38 +0,0 @@ -## Using the TPNS client - -```go -package main - -import ( - "fmt" - "time" - "github.com/philips-software/go-hsdp-api/tpns" -) - -func main() { - client, err := tpns.NewClient(&tpns.Config{ - Username: "Foo", - Password: "YourP@ssword!", - TPNSURL: "https://tpns.foo.com", - }) - if err != nil { - fmt.Printf("Error creating client: %v\n", err) - return - } - ok, resp, err := tpns.Messages.Push(&tpns.Message{ - Content: "YAY! It is working!", - PropositionID: "XYZ", - MessageType: "Push", - Targets: []string{"5b78e5f8-d73f-4712-aae0-355f6fa91752"}, - }) - if err != nil { - fmt.Printf("Error pushing: %v\n", err) - return - } - if !ok { - fmt.Printf("Error pushing: %d\n", resp.StatusCode) - return - } - fmt.Printf("Push success: %d\n", resp.StatusCode) -} -``` diff --git a/tpns/client.go b/tpns/client.go deleted file mode 100644 index 805cdfae..00000000 --- a/tpns/client.go +++ /dev/null @@ -1,234 +0,0 @@ -// Package tpns provides an interface for HSDP Third Party Notification Service (TPNS) -package tpns - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "strings" - - "github.com/philips-software/go-hsdp-api/internal" - - "github.com/google/go-querystring/query" -) - -const ( - userAgent = "go-hsdp-api/tpns/" + internal.LibraryVersion - tpnsAPIVersion = "2" -) - -// OptionFunc is the function signature function for options -type OptionFunc func(*http.Request) error - -// Config contains the configuration of a client -type Config struct { - TPNSURL string - Username string - Password string - Debug bool - DebugLog io.Writer -} - -// A Client manages communication with HSDP IAM API -type Client struct { - // HTTP client used to communicate with the API. - client *http.Client - - config *Config - - baseTPNSURL *url.URL - - // User agent used when communicating with the HSDP IAM API. - UserAgent string - - Messages *MessagesService -} - -// NewClient returns a new HSDP TDR API client. If a nil httpClient is -// provided, http.DefaultClient will be used. A configured IAM client must be provided -// as well -func NewClient(httpClient *http.Client, config *Config) (*Client, error) { - return newClient(httpClient, config) -} - -func newClient(httpClient *http.Client, config *Config) (*Client, error) { - if httpClient == nil { - httpClient = &http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - }, - } - } - c := &Client{client: httpClient, config: config, UserAgent: userAgent} - if err := c.SetBaseTPNSURL(c.config.TPNSURL); err != nil { - return nil, err - } - - if config.DebugLog != nil { - httpClient.Transport = internal.NewLoggingRoundTripper(httpClient.Transport, config.DebugLog) - } - - c.Messages = &MessagesService{client: c} - return c, nil -} - -// Close releases allocated resources of clients -func (c *Client) Close() { -} - -// SetBaseTPNSURL sets the base URL for API requests to a custom endpoint. urlStr -// should always be specified with a trailing slash. -func (c *Client) SetBaseTPNSURL(urlStr string) error { - if urlStr == "" { - return ErrBaseTPNSCannotBeEmpty - } - // Make sure the given URL end with a slash - if !strings.HasSuffix(urlStr, "/") { - urlStr += "/" - } - - var err error - c.baseTPNSURL, err = url.Parse(urlStr) - return err -} - -// NewTPNSRequest creates an new TPNS API request. A relative URL path can be provided in -// urlStr, in which case it is resolved relative to the base URL of the Client. -// Relative URL paths should always be specified without a preceding slash. If -// specified, the value pointed to by body is JSON encoded and included as the -// request body. -func (c *Client) NewTPNSRequest(method, path string, opt interface{}, options []OptionFunc) (*http.Request, error) { - u := *c.baseTPNSURL - // Set the encoded opaque data - u.Opaque = c.baseTPNSURL.Path + path - - if opt != nil { - q, err := query.Values(opt) - if err != nil { - return nil, err - } - u.RawQuery = q.Encode() - } - - req := &http.Request{ - Method: method, - URL: &u, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - Header: make(http.Header), - Host: u.Host, - } - - for _, fn := range options { - if fn == nil { - continue - } - - if err := fn(req); err != nil { - return nil, err - } - } - - if method == "POST" || method == "PUT" { - bodyBytes, err := json.Marshal(opt) - if err != nil { - return nil, err - } - bodyReader := bytes.NewReader(bodyBytes) - - u.RawQuery = "" - req.Body = io.NopCloser(bodyReader) - req.ContentLength = int64(bodyReader.Len()) - req.Header.Set("Content-Type", "application/json") - } - - req.Header.Set("Accept", "application/json") - req.Header.Set("Api-Version", tpnsAPIVersion) - req.SetBasicAuth(c.config.Username, c.config.Password) - - if c.UserAgent != "" { - req.Header.Set("User-Agent", c.UserAgent) - } - return req, nil -} - -// Response is a HSDP TPNS API response. This wraps the standard http.Response -// returned from HSDP TPNS and provides convenient access to things like errors -type Response struct { - *http.Response -} - -// newResponse creates a new Response for the provided http.Response. -func newResponse(r *http.Response) *Response { - response := &Response{Response: r} - return response -} - -// Do executes a http request. If v implements the io.Writer -// interface, the raw response body will be written to v, without attempting to -// first decode it. -func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) { - resp, err := c.client.Do(req) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - response := newResponse(resp) - - err = checkResponse(resp) - if err != nil { - // even though there was an error, we still return the response - // in case the caller wants to inspect it further - return response, err - } - - if v != nil { - if w, ok := v.(io.Writer); ok { - _, err = io.Copy(w, resp.Body) - } else { - err = json.NewDecoder(resp.Body).Decode(v) - } - } - - return response, err -} - -// ErrorResponse represents an IAM errors response -// containing a code and a human readable message -type ErrorResponse struct { - Response *http.Response `json:"-"` - Code string `json:"responseCode"` - Message string `json:"responseMessage"` -} - -func (e *ErrorResponse) Error() string { - path, _ := url.QueryUnescape(e.Response.Request.URL.Opaque) - u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, path) - return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message) -} - -// checkResponse checks the API response for errors, and returns them if present. -func checkResponse(r *http.Response) error { - switch r.StatusCode { - case 200, 201, 202, 204, 304: - return nil - } - - errorResponse := &ErrorResponse{Response: r} - data, err := io.ReadAll(r.Body) - if err == nil && data != nil { - var raw interface{} - if err := json.Unmarshal(data, &raw); err != nil { - errorResponse.Message = "failed to parse unknown error format" - } - - errorResponse.Message = parseError(raw) - } - - return errorResponse -} diff --git a/tpns/client_test.go b/tpns/client_test.go deleted file mode 100644 index 94b2d33e..00000000 --- a/tpns/client_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package tpns - -import ( - "encoding/json" - "io" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/stretchr/testify/assert" -) - -var ( - muxTPNS *http.ServeMux - serverTPNS *httptest.Server - tpnsClient *Client -) - -func setup(t *testing.T) func() { - muxTPNS = http.NewServeMux() - serverTPNS = httptest.NewServer(muxTPNS) - - tpnsClient, _ = NewClient(nil, &Config{ - TPNSURL: serverTPNS.URL, - }) - - muxTPNS.HandleFunc("/tpns/PushMessage", func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - t.Errorf("Expected ‘POST’ request") - } - if apiVersion := r.Header.Get("Api-Version"); apiVersion != "2" { - t.Errorf("Expected Api-Version = 2, got %s", apiVersion) - w.WriteHeader(http.StatusBadRequest) - return - } - var tpnsRequest Message - body, err := io.ReadAll(r.Body) - if err != nil { - t.Errorf("Error reading body") - w.WriteHeader(http.StatusInternalServerError) - return - } - err = json.Unmarshal(body, &tpnsRequest) - if err != nil { - t.Errorf("Invalid body in request") - w.WriteHeader(http.StatusBadRequest) - return - } - if tpnsRequest.MessageType == "" { - t.Errorf("Empty MessageType") - w.WriteHeader(http.StatusBadRequest) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - _, _ = io.WriteString(w, `{ - "issue": [ - { - "Severity": "information", - "Code": { - "coding": [ - { - "system": "MS", - "code": "201" - } - ] - }, - "Details": "Notification Sent" - } - ] - }`) - }) - - return func() { - serverTPNS.Close() - tpnsClient.Close() - } -} - -func TestDebug(t *testing.T) { - teardown := setup(t) - defer teardown() - - tmpfile, err := os.CreateTemp("", "example") - if err != nil { - t.Fatalf("Error: %v", err) - } - - tpnsClient, _ = NewClient(nil, &Config{ - TPNSURL: serverTPNS.URL, - Debug: true, - DebugLog: tmpfile, - }) - defer func() { - tpnsClient.Close() - _ = os.Remove(tmpfile.Name()) - }() - - _, _, _ = tpnsClient.Messages.Push(&Message{ - PropositionID: "XYZ", - MessageType: "Push", - Content: "YAY!", - Targets: []string{"foo"}, - }) - fi, err := tmpfile.Stat() - if !assert.Nil(t, err) { - return - } - assert.Less(t, int64(0), fi.Size()) -} - -func TestPush(t *testing.T) { - teardown := setup(t) - defer teardown() - - ok, resp, err := tpnsClient.Messages.Push(&Message{ - PropositionID: "XYZ", - MessageType: "Push", - Content: "YAY!", - Targets: []string{"foo"}, - }) - - if !ok { - t.Errorf("Expected call to succeed: %v", err) - return - } - if resp.StatusCode != http.StatusOK { - t.Errorf("Expected StatusOK") - return - } - if err != nil { - t.Errorf("Unexpected error: %v", err) - } -} diff --git a/tpns/coding.go b/tpns/coding.go deleted file mode 100644 index 1d7c1825..00000000 --- a/tpns/coding.go +++ /dev/null @@ -1,9 +0,0 @@ -package tpns - -type Coding struct { - System string `json:"system,omitempty"` - Version string `json:"version,omitempty"` - Code string `json:"code,omitempty"` - Display string `json:"display,omitempty"` - UserSelected *bool `json:"userSelected,omitempty"` -} diff --git a/tpns/errors.go b/tpns/errors.go deleted file mode 100644 index 02ee4a8b..00000000 --- a/tpns/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package tpns - -import ( - "errors" -) - -// Errors -var ( - ErrBaseTPNSCannotBeEmpty = errors.New("TPNS base URL cannot be empty") -) diff --git a/tpns/issue_response.go b/tpns/issue_response.go deleted file mode 100644 index b3c46477..00000000 --- a/tpns/issue_response.go +++ /dev/null @@ -1,18 +0,0 @@ -package tpns - -// Code describes a coding -type Code struct { - Coding []Coding `json:"coding"` -} - -// Issue describes an issue -type Issue struct { - Severity string `json:"Severity"` - Details string `json:"Details"` - Code Code `json:"Code"` -} - -// IssueResponse encapsulates one or more issues -type IssueResponse struct { - Issues []Issue `json:"issue"` -} diff --git a/tpns/messages_service.go b/tpns/messages_service.go deleted file mode 100644 index 90ff9543..00000000 --- a/tpns/messages_service.go +++ /dev/null @@ -1,36 +0,0 @@ -package tpns - -// MessagesService provides operations on TPNS messages -type MessagesService struct { - client *Client -} - -// Message describes a push message -type Message struct { - MessageType string `json:"MessageType"` - PropositionID string `json:"PropositionId"` - CustomProperties map[string]string `json:"CustomProperties"` - Lookup bool `json:"Lookup"` - Content string `json:"Content"` - Targets []string `json:"Targets"` -} - -// Push pushes a message to a mobile client -func (m *MessagesService) Push(msg *Message) (bool, *Response, error) { - req, err := m.client.NewTPNSRequest("POST", "tpns/PushMessage", msg, nil) - if err != nil { - return false, nil, err - } - - var responseStruct IssueResponse - - resp, err := m.client.Do(req, &responseStruct) - if err != nil { - return false, resp, err - } - - if err != nil { - return false, nil, err - } - return true, resp, nil -} diff --git a/tpns/parse.go b/tpns/parse.go deleted file mode 100644 index 60e20ab0..00000000 --- a/tpns/parse.go +++ /dev/null @@ -1,36 +0,0 @@ -package tpns - -import ( - "fmt" - "sort" - "strings" -) - -// parseError returns a string representation of an error struct -func parseError(raw interface{}) string { - switch raw := raw.(type) { - case string: - return raw - - case []interface{}: - var errs []string - for _, v := range raw { - errs = append(errs, parseError(v)) - } - return fmt.Sprintf("[%s]", strings.Join(errs, ", ")) - - case map[string]interface{}: - var errs []string - for k, v := range raw { - errs = append(errs, fmt.Sprintf("{%s: %s}", k, parseError(v))) - } - sort.Strings(errs) - return strings.Join(errs, ", ") - case float64: - return fmt.Sprintf("%d", int64(raw)) - case int64: - return fmt.Sprintf("%d", raw) - default: - return fmt.Sprintf("failed to parse unexpected error type: %T", raw) - } -} diff --git a/tpns/parse_test.go b/tpns/parse_test.go deleted file mode 100644 index e62b6775..00000000 --- a/tpns/parse_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package tpns - -import ( - "testing" -) - -func TestParseError(t *testing.T) { - var err1 = []interface{}{ - "Foo", - "Bar", - } - var err3 = map[string]interface{}{ - "Foo": "Bar", - } - parsed := parseError(err1) - if parsed != `[Foo, Bar]` { - t.Errorf("Unexpected parse output: `%s`", parsed) - } - parsed = parseError("err2") - if parsed != `err2` { - t.Errorf("Unexpected parse output: `%s`", parsed) - } - parsed = parseError(err3) - if parsed != `{Foo: Bar}` { - t.Errorf("Unexpected parse output: `%s`", parsed) - } - -}