github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/pkg/asc/asc.go (about)

     1  package asc
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	url2 "net/url"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	piperHttp "github.com/SAP/jenkins-library/pkg/http"
    15  	"github.com/SAP/jenkins-library/pkg/log"
    16  	"github.com/pkg/errors"
    17  	"github.com/sirupsen/logrus"
    18  )
    19  
    20  type App struct {
    21  	AppId    int    `json:"app_id"`
    22  	AppName  string `json:"app_name"`
    23  	BundleId string `json:"bundle_id"`
    24  	JamfId   string `json:"jamf_id"`
    25  }
    26  
    27  type JamfAppInformationResponse struct {
    28  	MobileDeviceApplication JamfMobileDeviceApplication `json:"mobile_device_application"`
    29  }
    30  
    31  type JamfMobileDeviceApplication struct {
    32  	General JamfMobileDeviceApplicationGeneral `json:"general"`
    33  }
    34  
    35  type JamfMobileDeviceApplicationGeneral struct {
    36  	Id int `json:"id"`
    37  }
    38  
    39  type CreateReleaseResponse struct {
    40  	Status  string  `json:"status"`
    41  	Message string  `json:"message"`
    42  	LastID  int     `json:"lastID"`
    43  	Data    Release `json:"data"`
    44  }
    45  
    46  type Release struct {
    47  	ReleaseID    int       `json:"release_id"`
    48  	AppID        int       `json:"app_id"`
    49  	Version      string    `json:"version"`
    50  	Description  string    `json:"description"`
    51  	ReleaseDate  time.Time `json:"release_date"`
    52  	SortOrder    any       `json:"sort_order"`
    53  	Visible      bool      `json:"visible"`
    54  	Created      time.Time `json:"created"`
    55  	FileMetadata any       `json:"file_metadata"`
    56  }
    57  
    58  // SystemInstance is the client communicating with the ASC backend
    59  type SystemInstance struct {
    60  	serverURL string
    61  	token     string
    62  	client    *piperHttp.Client
    63  	logger    *logrus.Entry
    64  }
    65  
    66  type System interface {
    67  	GetAppById(appId string) (App, error)
    68  	CreateRelease(ascAppId int, version string, description string, releaseDate string, visible bool) (CreateReleaseResponse, error)
    69  	GetJamfAppInfo(bundleId string, jamfTargetSystem string) (JamfAppInformationResponse, error)
    70  	UploadIpa(path string, jamfAppId int, jamfTargetSystem string, bundleId string, ascRelease Release) error
    71  }
    72  
    73  // NewSystemInstance returns a new ASC client for communicating with the backend
    74  func NewSystemInstance(client *piperHttp.Client, serverURL, token string) (*SystemInstance, error) {
    75  	loggerInstance := log.Entry().WithField("package", "SAP/jenkins-library/pkg/asc")
    76  
    77  	if len(serverURL) == 0 {
    78  		return nil, errors.New("serverUrl is not set but required")
    79  	}
    80  
    81  	if len(token) == 0 {
    82  		return nil, errors.New("AppToken is not set but required")
    83  	}
    84  
    85  	sys := &SystemInstance{
    86  		serverURL: strings.TrimSuffix(serverURL, "/"),
    87  		token:     token,
    88  		client:    client,
    89  		logger:    loggerInstance,
    90  	}
    91  
    92  	log.RegisterSecret(token)
    93  
    94  	options := piperHttp.ClientOptions{
    95  		Token:            fmt.Sprintf("Bearer %s", sys.token),
    96  		TransportTimeout: time.Second * 15,
    97  	}
    98  	sys.client.SetOptions(options)
    99  
   100  	return sys, nil
   101  }
   102  
   103  func sendRequest(sys *SystemInstance, method, url string, body io.Reader, header http.Header) ([]byte, error) {
   104  	var requestBody io.Reader
   105  	if body != nil {
   106  		closer := io.NopCloser(body)
   107  		bodyBytes, _ := io.ReadAll(closer)
   108  		requestBody = bytes.NewBuffer(bodyBytes)
   109  		defer closer.Close()
   110  	}
   111  	response, err := sys.client.SendRequest(method, fmt.Sprintf("%v/%v", sys.serverURL, url), requestBody, header, nil)
   112  	if err != nil && (response == nil) {
   113  		sys.logger.Errorf("HTTP request failed with error: %s", err)
   114  		return nil, err
   115  	}
   116  
   117  	data, _ := io.ReadAll(response.Body)
   118  	sys.logger.Debugf("Valid response body: %v", string(data))
   119  	defer response.Body.Close()
   120  	return data, nil
   121  }
   122  
   123  // GetAppById returns the app addressed by appId from the ASC backend
   124  func (sys *SystemInstance) GetAppById(appId string) (App, error) {
   125  	sys.logger.Debugf("Getting ASC App with ID %v...", appId)
   126  	var app App
   127  
   128  	data, err := sendRequest(sys, http.MethodGet, fmt.Sprintf("api/v1/apps/%v", appId), nil, nil)
   129  	if err != nil {
   130  		return app, errors.Wrapf(err, "fetching app %v failed", appId)
   131  	}
   132  
   133  	json.Unmarshal(data, &app)
   134  	return app, nil
   135  }
   136  
   137  // CreateRelease creates a release in ASC
   138  func (sys *SystemInstance) CreateRelease(ascAppId int, version string, description string, releaseDate string, visible bool) (CreateReleaseResponse, error) {
   139  
   140  	var createReleaseResponse CreateReleaseResponse
   141  
   142  	if len(releaseDate) == 0 {
   143  		currentTime := time.Now()
   144  		releaseDate = currentTime.Format("01/02/2006")
   145  	}
   146  
   147  	jsonData := map[string]string{
   148  		"version":      version,
   149  		"description":  description,
   150  		"release_date": releaseDate,
   151  		"visible":      strconv.FormatBool(visible),
   152  	}
   153  
   154  	jsonValue, err := json.Marshal(jsonData)
   155  	if err != nil {
   156  		return createReleaseResponse, errors.Wrap(err, "error marshalling release payload")
   157  	}
   158  
   159  	header := http.Header{}
   160  	header.Set("Content-Type", "application/json")
   161  
   162  	response, err := sendRequest(sys, http.MethodPost, fmt.Sprintf("api/v1/apps/%v/releases", ascAppId), bytes.NewBuffer(jsonValue), header)
   163  	if err != nil {
   164  		return createReleaseResponse, errors.Wrapf(err, "creating release")
   165  	}
   166  
   167  	json.Unmarshal(response, &createReleaseResponse)
   168  	return createReleaseResponse, nil
   169  }
   170  
   171  // GetJamfAppInfo fetches information about the app from Jamf
   172  func (sys *SystemInstance) GetJamfAppInfo(bundleId string, jamfTargetSystem string) (JamfAppInformationResponse, error) {
   173  
   174  	sys.logger.Debugf("Getting Jamf App Info by ID %v from jamf %v system...", bundleId, jamfTargetSystem)
   175  	var jamfAppInformationResponse JamfAppInformationResponse
   176  
   177  	data, err := sendRequest(sys, http.MethodPost, fmt.Sprintf("api/v1/jamf/%v/info?system=%v", bundleId, url2.QueryEscape(jamfTargetSystem)), nil, nil)
   178  	if err != nil {
   179  		return jamfAppInformationResponse, errors.Wrapf(err, "fetching jamf %v app info for %v failed", jamfTargetSystem, bundleId)
   180  	}
   181  
   182  	json.Unmarshal(data, &jamfAppInformationResponse)
   183  	return jamfAppInformationResponse, nil
   184  
   185  }
   186  
   187  // UploadIpa uploads the ipa to ASC and therewith to Jamf
   188  func (sys *SystemInstance) UploadIpa(path string, jamfAppId int, jamfTargetSystem string, bundleId string, ascRelease Release) error {
   189  
   190  	url := fmt.Sprintf("%v/api/v1/jamf/%v/ipa?app_id=%v&version=%v&system=%v&release_id=%v&bundle_id=%v", sys.serverURL, jamfAppId, ascRelease.AppID, url2.QueryEscape(ascRelease.Version), url2.QueryEscape(jamfTargetSystem), ascRelease.ReleaseID, url2.QueryEscape(bundleId))
   191  	_, err := sys.client.UploadFile(url, path, "file", nil, nil, "form")
   192  
   193  	if err != nil {
   194  		return errors.Wrap(err, "failed to upload ipa to asc")
   195  	}
   196  
   197  	return nil
   198  }