github.com/IBM-Cloud/bluemix-go@v0.0.0-20240314082800-4e02a69b84b2/api/mccp/mccpv2/apps.go (about)

     1  package mccpv2
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/IBM-Cloud/bluemix-go/bmxerror"
    10  	"github.com/IBM-Cloud/bluemix-go/client"
    11  	"github.com/IBM-Cloud/bluemix-go/helpers"
    12  	"github.com/IBM-Cloud/bluemix-go/rest"
    13  	"github.com/IBM-Cloud/bluemix-go/trace"
    14  )
    15  
    16  //AppState ...
    17  type AppState struct {
    18  	PackageState  string
    19  	InstanceState string
    20  }
    21  
    22  const (
    23  	//ErrCodeAppDoesnotExist ...
    24  	ErrCodeAppDoesnotExist = "AppADoesnotExist"
    25  
    26  	//AppRunningState ...
    27  	AppRunningState = "RUNNING"
    28  
    29  	//AppStartedState ...
    30  	AppStartedState = "STARTED"
    31  
    32  	//AppStagedState ...
    33  	AppStagedState = "STAGED"
    34  
    35  	//AppPendingState ...
    36  	AppPendingState = "PENDING"
    37  
    38  	//AppStoppedState ...
    39  	AppStoppedState = "STOPPED"
    40  
    41  	//AppFailedState ...
    42  	AppFailedState = "FAILED"
    43  
    44  	//AppUnKnownState ...
    45  	AppUnKnownState = "UNKNOWN"
    46  
    47  	//DefaultRetryDelayForStatusCheck ...
    48  	DefaultRetryDelayForStatusCheck = 10 * time.Second
    49  )
    50  
    51  //AppRequest ...
    52  type AppRequest struct {
    53  	Name                     *string                 `json:"name,omitempty"`
    54  	Memory                   int                     `json:"memory,omitempty"`
    55  	Instances                int                     `json:"instances,omitempty"`
    56  	DiskQuota                int                     `json:"disk_quota,omitempty"`
    57  	SpaceGUID                *string                 `json:"space_guid,omitempty"`
    58  	StackGUID                *string                 `json:"stack_guid,omitempty"`
    59  	State                    *string                 `json:"state,omitempty"`
    60  	DetectedStartCommand     *string                 `json:"detected_start_command,omitempty"`
    61  	Command                  *string                 `json:"command,omitempty"`
    62  	BuildPack                *string                 `json:"buildpack,omitempty"`
    63  	HealthCheckType          *string                 `json:"health_check_type,omitempty"`
    64  	HealthCheckTimeout       int                     `json:"health_check_timeout,omitempty"`
    65  	HealthCheckHTTPEndpoint  *string                 `json:"health_check_http_endpoint,omitempty"`
    66  	Diego                    bool                    `json:"diego,omitempty"`
    67  	EnableSSH                bool                    `json:"enable_ssh,omitempty"`
    68  	DockerImage              *string                 `json:"docker_image,omitempty"`
    69  	StagingFailedReason      *string                 `json:"staging_failed_reason,omitempty"`
    70  	StagingFailedDescription *string                 `json:"staging_failed_description,omitempty"`
    71  	Ports                    []int                   `json:"ports,omitempty"`
    72  	DockerCredentialsJSON    *map[string]interface{} `json:"docker_credentials_json,omitempty"`
    73  	EnvironmentJSON          *map[string]interface{} `json:"environment_json,omitempty"`
    74  }
    75  
    76  //AppEntity ...
    77  type AppEntity struct {
    78  	Name                     string                 `json:"name"`
    79  	SpaceGUID                string                 `json:"space_guid"`
    80  	StackGUID                string                 `json:"stack_guid"`
    81  	State                    string                 `json:"state"`
    82  	PackageState             string                 `json:"package_state"`
    83  	Memory                   int                    `json:"memory"`
    84  	Instances                int                    `json:"instances"`
    85  	DiskQuota                int                    `json:"disk_quota"`
    86  	Version                  string                 `json:"version"`
    87  	BuildPack                *string                `json:"buildpack"`
    88  	Command                  *string                `json:"command"`
    89  	Console                  bool                   `json:"console"`
    90  	Debug                    *string                `json:"debug"`
    91  	StagingTaskID            string                 `json:"staging_task_id"`
    92  	HealthCheckType          string                 `json:"health_check_type"`
    93  	HealthCheckTimeout       *int                   `json:"health_check_timeout"`
    94  	HealthCheckHTTPEndpoint  string                 `json:"health_check_http_endpoint"`
    95  	StagingFailedReason      string                 `json:"staging_failed_reason"`
    96  	StagingFailedDescription string                 `json:"staging_failed_description"`
    97  	Diego                    bool                   `json:"diego"`
    98  	DockerImage              *string                `json:"docker_image"`
    99  	EnableSSH                bool                   `json:"enable_ssh"`
   100  	Ports                    []int                  `json:"ports"`
   101  	DockerCredentialsJSON    map[string]interface{} `json:"docker_credentials_json"`
   102  	EnvironmentJSON          map[string]interface{} `json:"environment_json"`
   103  }
   104  
   105  //AppResource ...
   106  type AppResource struct {
   107  	Resource
   108  	Entity AppEntity
   109  }
   110  
   111  //AppFields ...
   112  type AppFields struct {
   113  	Metadata Metadata
   114  	Entity   AppEntity
   115  }
   116  
   117  //UploadBitsEntity ...
   118  type UploadBitsEntity struct {
   119  	GUID   string `json:"guid"`
   120  	Status string `json:"status"`
   121  }
   122  
   123  //UploadBitFields ...
   124  type UploadBitFields struct {
   125  	Metadata Metadata
   126  	Entity   UploadBitsEntity
   127  }
   128  
   129  //AppSummaryFields ...
   130  type AppSummaryFields struct {
   131  	GUID             string `json:"guid"`
   132  	Name             string `json:"name"`
   133  	State            string `json:"state"`
   134  	PackageState     string `json:"package_state"`
   135  	RunningInstances int    `json:"running_instances"`
   136  }
   137  
   138  //AppStats ...
   139  type AppStats struct {
   140  	State string `json:"state"`
   141  }
   142  
   143  //ToFields ..
   144  func (resource AppResource) ToFields() App {
   145  	entity := resource.Entity
   146  
   147  	return App{
   148  		GUID:                    resource.Metadata.GUID,
   149  		Name:                    entity.Name,
   150  		SpaceGUID:               entity.SpaceGUID,
   151  		StackGUID:               entity.StackGUID,
   152  		State:                   entity.State,
   153  		PackageState:            entity.PackageState,
   154  		Memory:                  entity.Memory,
   155  		Instances:               entity.Instances,
   156  		DiskQuota:               entity.DiskQuota,
   157  		Version:                 entity.Version,
   158  		BuildPack:               entity.BuildPack,
   159  		Command:                 entity.Command,
   160  		Console:                 entity.Console,
   161  		Debug:                   entity.Debug,
   162  		StagingTaskID:           entity.StagingTaskID,
   163  		HealthCheckType:         entity.HealthCheckType,
   164  		HealthCheckTimeout:      entity.HealthCheckTimeout,
   165  		HealthCheckHTTPEndpoint: entity.HealthCheckHTTPEndpoint,
   166  		Diego:                   entity.Diego,
   167  		DockerImage:             entity.DockerImage,
   168  		EnableSSH:               entity.EnableSSH,
   169  		Ports:                   entity.Ports,
   170  		DockerCredentialsJSON:   entity.DockerCredentialsJSON,
   171  		EnvironmentJSON:         entity.EnvironmentJSON,
   172  	}
   173  }
   174  
   175  //App model
   176  type App struct {
   177  	Name                    string
   178  	SpaceGUID               string
   179  	GUID                    string
   180  	StackGUID               string
   181  	State                   string
   182  	PackageState            string
   183  	Memory                  int
   184  	Instances               int
   185  	DiskQuota               int
   186  	Version                 string
   187  	BuildPack               *string
   188  	Command                 *string
   189  	Console                 bool
   190  	Debug                   *string
   191  	StagingTaskID           string
   192  	HealthCheckType         string
   193  	HealthCheckTimeout      *int
   194  	HealthCheckHTTPEndpoint string
   195  	Diego                   bool
   196  	DockerImage             *string
   197  	EnableSSH               bool
   198  	Ports                   []int
   199  	DockerCredentialsJSON   map[string]interface{}
   200  	EnvironmentJSON         map[string]interface{}
   201  }
   202  
   203  //Apps ...
   204  type Apps interface {
   205  	Create(appPayload AppRequest, opts ...bool) (*AppFields, error)
   206  	List() ([]App, error)
   207  	Get(appGUID string) (*AppFields, error)
   208  	Update(appGUID string, appPayload AppRequest, opts ...bool) (*AppFields, error)
   209  	Delete(appGUID string, opts ...bool) error
   210  	FindByName(spaceGUID, name string) (*App, error)
   211  	Start(appGUID string, timeout time.Duration) (*AppState, error)
   212  	Upload(path string, name string, opts ...bool) (*UploadBitFields, error)
   213  	Summary(appGUID string) (*AppSummaryFields, error)
   214  	Stat(appGUID string) (map[string]AppStats, error)
   215  	WaitForAppStatus(waitForThisState, appGUID string, timeout time.Duration) (string, error)
   216  	WaitForInstanceStatus(waitForThisState, appGUID string, timeout time.Duration) (string, error)
   217  	Instances(appGUID string) (map[string]AppStats, error)
   218  	Restage(appGUID string, timeout time.Duration) (*AppState, error)
   219  	WaitForStatus(appGUID string, maxWaitTime time.Duration) (*AppState, error)
   220  
   221  	//Routes related
   222  	BindRoute(appGUID, routeGUID string) (*AppFields, error)
   223  	ListRoutes(appGUID string) ([]Route, error)
   224  	UnBindRoute(appGUID, routeGUID string) error
   225  
   226  	//Service bindings
   227  	ListServiceBindings(appGUID string) ([]ServiceBinding, error)
   228  	DeleteServiceBindings(appGUID string, bindingGUIDs ...string) error
   229  }
   230  
   231  type app struct {
   232  	client *client.Client
   233  }
   234  
   235  func newAppAPI(c *client.Client) Apps {
   236  	return &app{
   237  		client: c,
   238  	}
   239  }
   240  
   241  func (r *app) FindByName(spaceGUID string, name string) (*App, error) {
   242  	rawURL := fmt.Sprintf("/v2/spaces/%s/apps", spaceGUID)
   243  	req := rest.GetRequest(rawURL).Query("q", "name:"+name)
   244  	httpReq, err := req.Build()
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	path := httpReq.URL.String()
   249  	apps, err := r.listAppWithPath(path)
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  	if len(apps) == 0 {
   254  		return nil, bmxerror.New(ErrCodeAppDoesnotExist,
   255  			fmt.Sprintf("Given app:  %q doesn't exist in given space: %q", name, spaceGUID))
   256  
   257  	}
   258  	return &apps[0], nil
   259  }
   260  
   261  // opts is list of boolean parametes
   262  // opts[0] - async - Will run the create request in a background job. Recommended: 'true'. Default to 'true'.
   263  func (r *app) Create(appPayload AppRequest, opts ...bool) (*AppFields, error) {
   264  	async := true
   265  	if len(opts) > 0 {
   266  		async = opts[0]
   267  	}
   268  	rawURL := fmt.Sprintf("/v2/apps?async=%t", async)
   269  	appFields := AppFields{}
   270  	_, err := r.client.Post(rawURL, appPayload, &appFields)
   271  	if err != nil {
   272  		return nil, err
   273  	}
   274  	return &appFields, nil
   275  }
   276  
   277  func (r *app) BindRoute(appGUID, routeGUID string) (*AppFields, error) {
   278  	rawURL := fmt.Sprintf("/v2/apps/%s/routes/%s", appGUID, routeGUID)
   279  	appFields := AppFields{}
   280  	_, err := r.client.Put(rawURL, nil, &appFields)
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  	return &appFields, nil
   285  }
   286  
   287  func (r *app) ListRoutes(appGUID string) ([]Route, error) {
   288  	rawURL := fmt.Sprintf("/v2/apps/%s/routes", appGUID)
   289  	req := rest.GetRequest(rawURL)
   290  	httpReq, err := req.Build()
   291  	if err != nil {
   292  		return nil, err
   293  	}
   294  	path := httpReq.URL.String()
   295  	route, err := listRouteWithPath(r.client, path)
   296  	if err != nil {
   297  		return nil, err
   298  	}
   299  	return route, nil
   300  }
   301  
   302  func (r *app) UnBindRoute(appGUID, routeGUID string) error {
   303  	rawURL := fmt.Sprintf("/v2/apps/%s/routes/%s", appGUID, routeGUID)
   304  	_, err := r.client.Delete(rawURL)
   305  	return err
   306  }
   307  
   308  func (r *app) DeleteServiceBindings(appGUID string, sbGUIDs ...string) error {
   309  	for _, g := range sbGUIDs {
   310  		rawURL := fmt.Sprintf("/v2/apps/%s/service_bindings/%s", appGUID, g)
   311  		_, err := r.client.Delete(rawURL)
   312  		return err
   313  	}
   314  	return nil
   315  }
   316  
   317  func (r *app) listAppWithPath(path string) ([]App, error) {
   318  	var apps []App
   319  	_, err := r.client.GetPaginated(path, NewCCPaginatedResources(AppResource{}), func(resource interface{}) bool {
   320  		if appResource, ok := resource.(AppResource); ok {
   321  			apps = append(apps, appResource.ToFields())
   322  			return true
   323  		}
   324  		return false
   325  	})
   326  	return apps, err
   327  }
   328  
   329  // opts is list of boolean parametes
   330  // opts[0] - async - If true, a new asynchronous job is submitted to persist the bits and the job id is included in the response.
   331  // The client will need to poll the job's status until persistence is completed successfully.
   332  // If false, the request will block until the bits are persisted synchronously. Defaults to 'false'.
   333  
   334  func (r *app) Upload(appGUID string, zipPath string, opts ...bool) (*UploadBitFields, error) {
   335  	async := false
   336  	if len(opts) > 0 {
   337  		async = opts[0]
   338  	}
   339  	req := rest.PutRequest(r.client.URL("/v2/apps/"+appGUID+"/bits")).Query("async", strconv.FormatBool(async))
   340  	file, err := os.Open(zipPath)
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  	defer file.Close()
   345  
   346  	f := rest.File{
   347  		Name:    file.Name(),
   348  		Content: file,
   349  	}
   350  	req.File("application", f)
   351  	req.Field("resources", "[]")
   352  	uploadBitResponse := &UploadBitFields{}
   353  	_, err = r.client.SendRequest(req, uploadBitResponse)
   354  	return uploadBitResponse, err
   355  }
   356  
   357  func (r *app) Start(appGUID string, maxWaitTime time.Duration) (*AppState, error) {
   358  	payload := AppRequest{
   359  		State: helpers.String(AppStartedState),
   360  	}
   361  	rawURL := fmt.Sprintf("/v2/apps/%s", appGUID)
   362  	appFields := AppFields{}
   363  	_, err := r.client.Put(rawURL, payload, &appFields)
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  	appState := &AppState{
   368  		PackageState:  AppPendingState,
   369  		InstanceState: AppUnKnownState,
   370  	}
   371  	if maxWaitTime == 0 {
   372  		appState.PackageState = appFields.Entity.PackageState
   373  		appState.InstanceState = appFields.Entity.State
   374  		return appState, nil
   375  	}
   376  	return r.WaitForStatus(appGUID, maxWaitTime)
   377  
   378  }
   379  
   380  func (r *app) Get(appGUID string) (*AppFields, error) {
   381  	rawURL := fmt.Sprintf("/v2/apps/%s", appGUID)
   382  	appFields := AppFields{}
   383  	_, err := r.client.Get(rawURL, &appFields, nil)
   384  	if err != nil {
   385  		return nil, err
   386  	}
   387  	return &appFields, nil
   388  }
   389  
   390  func (r *app) Summary(appGUID string) (*AppSummaryFields, error) {
   391  	rawURL := fmt.Sprintf("/v2/apps/%s/summary", appGUID)
   392  	appFields := AppSummaryFields{}
   393  	_, err := r.client.Get(rawURL, &appFields, nil)
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  	return &appFields, nil
   398  }
   399  
   400  func (r *app) Stat(appGUID string) (map[string]AppStats, error) {
   401  	rawURL := fmt.Sprintf("/v2/apps/%s/stats", appGUID)
   402  	appStats := map[string]AppStats{}
   403  	_, err := r.client.Get(rawURL, &appStats, nil)
   404  	if err != nil {
   405  		return nil, err
   406  	}
   407  	return appStats, nil
   408  }
   409  
   410  func (r *app) Instances(appGUID string) (map[string]AppStats, error) {
   411  
   412  	rawURL := fmt.Sprintf("/v2/apps/%s/instances", appGUID)
   413  	appInstances := map[string]AppStats{}
   414  	_, err := r.client.Get(rawURL, &appInstances, nil)
   415  	if err != nil {
   416  		return nil, err
   417  	}
   418  	return appInstances, nil
   419  }
   420  
   421  func (r *app) List() ([]App, error) {
   422  	rawURL := "v2/apps"
   423  	req := rest.GetRequest(rawURL)
   424  	httpReq, err := req.Build()
   425  	if err != nil {
   426  		return nil, err
   427  	}
   428  	path := httpReq.URL.String()
   429  	apps, err := r.listAppWithPath(path)
   430  	if err != nil {
   431  		return nil, err
   432  	}
   433  	return apps, nil
   434  
   435  }
   436  
   437  // opts is list of boolean parametes
   438  // opts[0] - async - Will run the update request in a background job. Recommended: 'true'. Default to 'true'.
   439  func (r *app) Update(appGUID string, appPayload AppRequest, opts ...bool) (*AppFields, error) {
   440  	async := true
   441  	if len(opts) > 0 {
   442  		async = opts[0]
   443  	}
   444  	rawURL := fmt.Sprintf("/v2/apps/%s?async=%t", appGUID, async)
   445  	appFields := AppFields{}
   446  	_, err := r.client.Put(rawURL, appPayload, &appFields)
   447  	if err != nil {
   448  		return nil, err
   449  	}
   450  	return &appFields, nil
   451  }
   452  
   453  // opts is list of boolean parametes
   454  // opts[0] - async - Will run the delete request in a background job. Recommended: 'true'. Default to 'true'.
   455  // opts[1] - recursive - Will delete service bindings, and routes associated with the app. Default to 'false'.
   456  func (r *app) Delete(appGUID string, opts ...bool) error {
   457  	async := true
   458  	recursive := false
   459  	if len(opts) > 0 {
   460  		async = opts[0]
   461  	}
   462  	if len(opts) > 1 {
   463  		recursive = opts[1]
   464  	}
   465  	rawURL := fmt.Sprintf("/v2/apps/%s?async=%t&recursive=%t", appGUID, async, recursive)
   466  	_, err := r.client.Delete(rawURL)
   467  	return err
   468  }
   469  
   470  func (r *app) Restage(appGUID string, maxWaitTime time.Duration) (*AppState, error) {
   471  	rawURL := fmt.Sprintf("/v2/apps/%s/restage", appGUID)
   472  	appFields := AppFields{}
   473  	_, err := r.client.Post(rawURL, nil, &appFields)
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  	appState := &AppState{
   478  		PackageState:  AppPendingState,
   479  		InstanceState: AppUnKnownState,
   480  	}
   481  	if maxWaitTime == 0 {
   482  		appState.PackageState = appFields.Entity.PackageState
   483  		appState.InstanceState = appFields.Entity.State
   484  		return appState, nil
   485  	}
   486  	return r.WaitForStatus(appGUID, maxWaitTime)
   487  
   488  }
   489  
   490  func (r *app) WaitForAppStatus(waitForThisState, appGUID string, maxWaitTime time.Duration) (string, error) {
   491  	timeout := time.After(maxWaitTime)
   492  	tick := time.NewTicker(DefaultRetryDelayForStatusCheck)
   493  	defer tick.Stop()
   494  	status := AppPendingState
   495  	for {
   496  		select {
   497  		case <-timeout:
   498  			trace.Logger.Printf("Timed out while checking the app status for %q.  Waited for %q for the state to be %q", appGUID, maxWaitTime, waitForThisState)
   499  			return status, nil
   500  		case <-tick.C:
   501  			appFields, err := r.Get(appGUID)
   502  			if err != nil {
   503  				return "", err
   504  			}
   505  			status = appFields.Entity.PackageState
   506  			trace.Logger.Println("apps.Entity.PackageState  ===>>> ", status)
   507  			if status == waitForThisState || status == AppFailedState {
   508  				return status, nil
   509  			}
   510  		}
   511  	}
   512  }
   513  
   514  func (r *app) WaitForInstanceStatus(waitForThisState, appGUID string, maxWaitTime time.Duration) (string, error) {
   515  	timeout := time.After(maxWaitTime)
   516  	tick := time.NewTicker(DefaultRetryDelayForStatusCheck)
   517  	defer tick.Stop()
   518  	status := AppStartedState
   519  	for {
   520  		select {
   521  		case <-timeout:
   522  			trace.Logger.Printf("Timed out while checking the app status for %q. Waited for %q for the state to be %q", appGUID, maxWaitTime, waitForThisState)
   523  			return status, nil
   524  		case <-tick.C:
   525  			appStat, err := r.Stat(appGUID)
   526  			if err != nil {
   527  				return status, err
   528  			}
   529  			stateCount := 0
   530  			for k, v := range appStat {
   531  				fmt.Printf("Instance[%s] State is %s", k, v)
   532  				if v.State == waitForThisState {
   533  					stateCount++
   534  				}
   535  			}
   536  			if stateCount == len(appStat) {
   537  				return waitForThisState, nil
   538  			}
   539  
   540  		}
   541  	}
   542  
   543  }
   544  
   545  func (r *app) WaitForStatus(appGUID string, maxWaitTime time.Duration) (*AppState, error) {
   546  	appState := &AppState{
   547  		PackageState:  AppPendingState,
   548  		InstanceState: AppUnKnownState,
   549  	}
   550  	status, err := r.WaitForAppStatus(AppStagedState, appGUID, maxWaitTime/2)
   551  	appState.PackageState = status
   552  	if err != nil || status == AppFailedState {
   553  		return appState, err
   554  	}
   555  	status, err = r.WaitForInstanceStatus(AppRunningState, appGUID, maxWaitTime/2)
   556  	appState.InstanceState = status
   557  	return appState, err
   558  }
   559  
   560  //TODO pull the wait logic in a auxiliary function which can be used by all
   561  
   562  func (r *app) ListServiceBindings(appGUID string) ([]ServiceBinding, error) {
   563  	rawURL := fmt.Sprintf("/v2/apps/%s/service_bindings", appGUID)
   564  	req := rest.GetRequest(rawURL)
   565  	httpReq, err := req.Build()
   566  	if err != nil {
   567  		return nil, err
   568  	}
   569  	path := httpReq.URL.String()
   570  	sb, err := listServiceBindingWithPath(r.client, path)
   571  	if err != nil {
   572  		return nil, err
   573  	}
   574  	return sb, nil
   575  }