diff --git a/bitriseclient/bitriseclient.go b/bitriseclient/bitriseclient.go new file mode 100644 index 00000000..a6ad13fa --- /dev/null +++ b/bitriseclient/bitriseclient.go @@ -0,0 +1,243 @@ +package bitriseclient + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "strings" + "time" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/retry" + "github.com/bitrise-io/go-utils/urlutil" +) + +const ( + baseURL = "https://api.bitrise.io/v0.1/" + appsEndPoint = "/apps" + provisioningProfilesEndPoint = "/provisioning-profiles" + certificatesEndPoint = "/build-certificates" +) + +// Paging ... +type Paging struct { + TotalItemCount int `json:"total_item_count"` + PageItemLimit int `json:"page_item_limit"` + Next string `json:"next"` +} + +// Owner ... +type Owner struct { + AccountType string `json:"account_type"` + Name string `json:"name"` + Slug string `json:"slug"` +} + +// Application ... +type Application struct { + Slug string `json:"slug"` + Title string `json:"title"` + ProjectType string `json:"project_type"` + Provider string `json:"provider"` + RepoOwner string `json:"repo_owner"` + RepoURL string `json:"repo_url"` + RepoSlug string `json:"repo_slug"` + IsDisabled bool `json:"is_disabled"` + Status int `json:"status"` + IsPublic bool `json:"is_public"` + Owner Owner `json:"owner"` +} + +// MyAppsResponse ... +type MyAppsResponse struct { + Data []Application `json:"data"` + Paging Paging `json:"paging"` +} + +// BitriseClient ... +type BitriseClient struct { + accessToken string + selectedAppSlug string + headers map[string]string + client http.Client +} + +// NewBitriseClient ... +func NewBitriseClient(accessToken string) (*BitriseClient, []Application, error) { + client := &BitriseClient{accessToken, "", map[string]string{"Authorization": "token " + accessToken}, http.Client{}} + var apps []Application + + log.Infof("Fetching your application list from Bitrise...") + + requestURL, err := urlutil.Join(baseURL, appsEndPoint) + if err != nil { + return nil, nil, err + } + + log.Debugf("\nRequest URL: %s", requestURL) + + // Response struct + var appListResponse MyAppsResponse + stillPaging := true + var next string + + for stillPaging { + headers := client.headers + + request, err := createRequest(http.MethodGet, requestURL, headers, nil) + if err != nil { + return nil, nil, err + } + + if len(next) > 0 { + quearryValues := request.URL.Query() + quearryValues.Add("next", next) + request.URL.RawQuery = quearryValues.Encode() + } + + // Perform request + response, _, err := RunRequest(client, request, &appListResponse) + if err != nil { + return nil, nil, err + } + + appListResponse = *response.(*MyAppsResponse) + apps = append(apps, appListResponse.Data...) + + if len(appListResponse.Paging.Next) > 0 { + next = appListResponse.Paging.Next + appListResponse = MyAppsResponse{} + } else { + stillPaging = false + } + } + + return client, apps, nil +} + +// SetSelectedAppSlug ... +func (client *BitriseClient) SetSelectedAppSlug(slug string) { + client.selectedAppSlug = slug +} + +// RunRequest ... +func RunRequest(client *BitriseClient, req *http.Request, requestResponse interface{}) (interface{}, []byte, error) { + var responseBody []byte + + if err := retry.Times(1).Wait(5 * time.Second).Try(func(attempt uint) error { + body, statusCode, err := performRequest(client, req) + if err != nil { + log.Warnf("Attempt (%d) failed, error: %s", attempt+1, err) + if !strings.Contains(err.Error(), "failed to perform request") { + log.Warnf("Response status: %d", statusCode) + log.Warnf("Body: %s", string(body)) + } + return err + } + + // Parse JSON body + if requestResponse != nil { + if err := json.Unmarshal([]byte(body), &requestResponse); err != nil { + return fmt.Errorf("failed to unmarshal response (%s), error: %s", body, err) + } + + logDebugPretty(&requestResponse) + } + responseBody = body + + return nil + }); err != nil { + return nil, nil, err + } + + return requestResponse, responseBody, nil +} + +func createUploadRequest(requestMethod string, url string, headers map[string]string, filePth string) (*http.Request, error) { + var content []byte + + f, err := os.Open(filePth) + if err != nil { + return nil, err + + } + + content, err = ioutil.ReadAll(f) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(content)) + if err != nil { + return nil, err + } + addHeaders(req, headers) + + return req, nil +} + +func createRequest(requestMethod string, url string, headers map[string]string, fields map[string]interface{}) (*http.Request, error) { + var b bytes.Buffer + + if len(fields) > 0 { + err := json.NewEncoder(&b).Encode(fields) + if err != nil { + return nil, err + } + } + + log.Debugf("Request body: %s", string(b.Bytes())) + + req, err := http.NewRequest(requestMethod, url, bytes.NewReader(b.Bytes())) + if err != nil { + return nil, err + } + addHeaders(req, headers) + + return req, nil +} + +func performRequest(bitriseClient *BitriseClient, request *http.Request) (body []byte, statusCode int, err error) { + response, err := bitriseClient.client.Do(request) + if err != nil { + // On error, any Response can be ignored + return nil, -1, fmt.Errorf("failed to perform request, error: %s", err) + } + + // The client must close the response body when finished with it + defer func() { + if cerr := response.Body.Close(); err != nil { + cerr = fmt.Errorf("Failed to close response body, error: %s", cerr) + } + }() + + body, err = ioutil.ReadAll(response.Body) + if err != nil { + return []byte{}, response.StatusCode, fmt.Errorf("failed to read response body, error: %s", err) + } + + if response.StatusCode < http.StatusOK || response.StatusCode > http.StatusMultipleChoices { + return body, response.StatusCode, errors.New("non success status code") + } + + return body, response.StatusCode, nil +} + +func addHeaders(req *http.Request, headers map[string]string) { + for key, value := range headers { + req.Header.Add(key, value) + } +} + +func logDebugPretty(v interface{}) { + indentedBytes, err := json.MarshalIndent(v, "", " ") + if err != nil { + fmt.Println("error:", err) + } + + log.Debugf("Response: %+v\n", string(indentedBytes)) +} diff --git a/bitriseclient/certificate.go b/bitriseclient/certificate.go new file mode 100644 index 00000000..0644f1c9 --- /dev/null +++ b/bitriseclient/certificate.go @@ -0,0 +1,263 @@ +package bitriseclient + +import ( + "fmt" + "math/big" + "net/http" + "path/filepath" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/urlutil" + "github.com/bitrise-tools/go-xcode/certificateutil" +) + +// RegisterIdentityData ... +type RegisterIdentityData struct { + UploadFileName string `json:"upload_file_name"` + UploadFileSize int64 `json:"upload_file_size"` + Slug string `json:"slug"` + Processed bool `json:"processed"` + IsExpose bool `json:"is_expose"` + IsProtected bool `json:"is_protected"` + UploadURL string `json:"upload_url"` +} + +// RegisterIdentityResponse ... +type RegisterIdentityResponse struct { + Data RegisterIdentityData `json:"data"` +} + +// ConfirmIdentityUploadData ... +type ConfirmIdentityUploadData struct { + UploadFileName string `json:"upload_file_name"` + UploadFileSize int `json:"upload_file_size"` + Slug string `json:"slug"` + Processed bool `json:"processed"` + CertificatePassword string `json:"certificate_password"` + IsExpose bool `json:"is_expose"` + IsProtected bool `json:"dais_protectedta"` +} + +// ConfirmIdentityUploadResponse ... +type ConfirmIdentityUploadResponse struct { + Data ConfirmIdentityUploadData `json:"data"` +} + +// IdentityListData ... +type IdentityListData struct { + UploadFileName string `json:"upload_file_name"` + UploadFileSize int `json:"upload_file_size"` + Slug string `json:"slug"` + Processed bool `json:"processed"` + CertificatePassword string `json:"certificate_password"` + IsExpose bool `json:"is_expose"` + IsProtected bool `json:"dais_protectedta"` +} + +// IdentityListResponse ... +type IdentityListResponse struct { + Data []IdentityListData `json:"data"` +} + +// IdentityData ... +type IdentityData struct { + UploadFileName string `json:"upload_file_name"` + UploadFileSize int `json:"upload_file_size"` + Slug string `json:"slug"` + Processed bool `json:"processed"` + CertificatePassword string `json:"certificate_password"` + IsExpose bool `json:"is_expose"` + IsProtected bool `json:"dais_protectedta"` + DownloadURL string `json:"download_url"` +} + +// IdentityResponse ... +type IdentityResponse struct { + Data IdentityData `json:"data"` +} + +// FetchUploadedIdentities ... +func (client *BitriseClient) FetchUploadedIdentities() ([]IdentityListData, error) { + log.Debugf("\nDownloading provisioning profile list from Bitrise...") + + requestURL, err := urlutil.Join(baseURL, appsEndPoint, client.selectedAppSlug, certificatesEndPoint) + if err != nil { + return []IdentityListData{}, err + } + + request, err := createRequest(http.MethodGet, requestURL, client.headers, nil) + if err != nil { + return []IdentityListData{}, err + } + log.Debugf("\nRequest URL: %s", requestURL) + + // Response struct + var requestResponse IdentityListResponse + // + // Perform request + response, _, err := RunRequest(client, request, &requestResponse) + if err != nil { + return nil, err + } + + requestResponse = *response.(*IdentityListResponse) + return requestResponse.Data, nil +} + +// GetUploadedCertificatesSerialby ... +func (client *BitriseClient) GetUploadedCertificatesSerialby(identitySlug string) (certificateSerialList []big.Int, err error) { + downloadURL, certificatePassword, err := client.getUploadedIdentityDownloadURLBy(identitySlug) + if err != nil { + return nil, err + } + + content, err := client.downloadUploadedIdentity(downloadURL) + if err != nil { + return nil, err + } + + certificates, err := certificateutil.CertificatesFromPKCS12Content([]byte(content), certificatePassword) + if err != nil { + return nil, err + } + + var serialList []big.Int + + for _, certificate := range certificates { + serialList = append(serialList, *certificate.SerialNumber) + } + return serialList, nil +} + +func (client *BitriseClient) getUploadedIdentityDownloadURLBy(certificateSlug string) (downloadURL string, password string, err error) { + log.Debugf("\nGet downloadURL for certificate (slug - %s) from Bitrise...", certificateSlug) + + requestURL, err := urlutil.Join(baseURL, appsEndPoint, client.selectedAppSlug, certificatesEndPoint, certificateSlug) + if err != nil { + return "", "", err + } + + log.Debugf("\nRequest URL: %s", requestURL) + + request, err := createRequest(http.MethodGet, requestURL, client.headers, nil) + if err != nil { + return "", "", err + } + + // Response struct + var requestResponse IdentityResponse + + // + // Perform request + response, _, err := RunRequest(client, request, &requestResponse) + if err != nil { + return "", "", err + } + + requestResponse = *response.(*IdentityResponse) + return requestResponse.Data.DownloadURL, requestResponse.Data.CertificatePassword, nil +} + +func (client *BitriseClient) downloadUploadedIdentity(downloadURL string) (content string, err error) { + log.Debugf("\nDownloading identities from Bitrise...") + log.Debugf("\nRequest URL: %s", downloadURL) + + request, err := createRequest(http.MethodGet, downloadURL, nil, nil) + if err != nil { + return "", err + } + + // Response struct + var requestResponse string + + // + // Perform request + _, body, err := RunRequest(client, request, nil) + if err != nil { + return "", err + } + + requestResponse = string(body) + return requestResponse, nil + +} + +// RegisterIdentity ... +func (client *BitriseClient) RegisterIdentity(certificateSize int64) (RegisterIdentityData, error) { + fmt.Println() + log.Infof("Register %s on Bitrise...", "Identities.p12") + + requestURL, err := urlutil.Join(baseURL, appsEndPoint, client.selectedAppSlug, certificatesEndPoint) + if err != nil { + return RegisterIdentityData{}, err + } + + log.Debugf("\nRequest URL: %s", requestURL) + + fields := map[string]interface{}{ + "upload_file_name": "Identities.p12", + "upload_file_size": certificateSize, + } + + request, err := createRequest(http.MethodPost, requestURL, client.headers, fields) + if err != nil { + return RegisterIdentityData{}, err + } + + // Response struct + var requestResponse RegisterIdentityResponse + + // + // Perform request + response, _, err := RunRequest(client, request, &requestResponse) + if err != nil { + return RegisterIdentityData{}, err + } + + requestResponse = *response.(*RegisterIdentityResponse) + return requestResponse.Data, nil +} + +// UploadIdentity ... +func (client *BitriseClient) UploadIdentity(uploadURL string, uploadFileName string, outputDirPath string, exportFileName string) error { + fmt.Println() + log.Infof("Upload %s to Bitrise...", exportFileName) + + filePth := filepath.Join(outputDirPath, exportFileName) + + request, err := createUploadRequest(http.MethodPut, uploadURL, nil, filePth) + if err != nil { + return err + } + + _, _, err = RunRequest(client, request, nil) + if err != nil { + return err + } + return nil +} + +// ConfirmIdentityUpload ... +func (client *BitriseClient) ConfirmIdentityUpload(certificateSlug string, certificateUploadName string) error { + fmt.Println() + log.Infof("Confirm - %s - upload to Bitrise...", certificateUploadName) + + requestURL, err := urlutil.Join(baseURL, appsEndPoint, client.selectedAppSlug, "build-certificates", certificateSlug, "uploaded") + if err != nil { + return err + } + + request, err := createRequest(http.MethodPost, requestURL, client.headers, nil) + if err != nil { + return err + } + + // Response struct + requestResponse := ConfirmProvProfileUploadResponse{} + + _, _, err = RunRequest(client, request, &requestResponse) + if err != nil { + return err + } + return nil +} diff --git a/bitriseclient/profile.go b/bitriseclient/profile.go new file mode 100644 index 00000000..c0cf058e --- /dev/null +++ b/bitriseclient/profile.go @@ -0,0 +1,268 @@ +package bitriseclient + +import ( + "fmt" + "net/http" + "path/filepath" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-utils/urlutil" + "github.com/bitrise-tools/go-xcode/profileutil" +) + +// RegisterProvisioningProfileData ... +type RegisterProvisioningProfileData struct { + UploadFileName string `json:"upload_file_name"` + UploadFileSize int64 `json:"upload_file_size"` + Slug string `json:"slug"` + Processed bool `json:"processed"` + IsExpose bool `json:"is_expose"` + IsProtected bool `json:"is_protected"` + UploadURL string `json:"upload_url"` +} + +// RegisterProvisioningProfileResponse ... +type RegisterProvisioningProfileResponse struct { + Data RegisterProvisioningProfileData `json:"data"` +} + +// ConfirmProvProfileUploadData ... +type ConfirmProvProfileUploadData struct { + UploadFileName string `json:"upload_file_name"` + UploadFileSize int `json:"upload_file_size"` + Slug string `json:"slug"` + Processed bool `json:"processed"` + IsExpose bool `json:"is_expose"` + IsProtected bool `json:"dais_protectedta"` +} + +// ConfirmProvProfileUploadResponse ... +type ConfirmProvProfileUploadResponse struct { + Data ConfirmProvProfileUploadData `json:"data"` +} + +// ProvisioningProfileListData ... +type ProvisioningProfileListData struct { + UploadFileName string `json:"upload_file_name"` + UploadFileSize int `json:"upload_file_size"` + Slug string `json:"slug"` + Processed bool `json:"processed"` + IsExpose bool `json:"is_expose"` + IsProtected bool `json:"dais_protectedta"` +} + +// ProvisioningProfileListResponse ... +type ProvisioningProfileListResponse struct { + Data []ProvisioningProfileListData `json:"data"` +} + +// UploadedProvisioningProfileData ... +type UploadedProvisioningProfileData struct { + UploadFileName string `json:"upload_file_name"` + UploadFileSize int `json:"upload_file_size"` + Slug string `json:"slug"` + Processed bool `json:"processed"` + IsExpose bool `json:"is_expose"` + IsProtected bool `json:"dais_protectedta"` + DownloadURL string `json:"download_url"` +} + +// UploadedProvisioningProfileResponse ... +type UploadedProvisioningProfileResponse struct { + Data UploadedProvisioningProfileData `json:"data"` +} + +// FetchProvisioningProfiles ... +func (client *BitriseClient) FetchProvisioningProfiles() ([]ProvisioningProfileListData, error) { + log.Debugf("\nDownloading provisioning profile list from Bitrise...") + + requestURL, err := urlutil.Join(baseURL, appsEndPoint, client.selectedAppSlug, provisioningProfilesEndPoint) + if err != nil { + return nil, err + } + + log.Debugf("\nRequest URL: %s", requestURL) + + request, err := createRequest(http.MethodGet, requestURL, client.headers, nil) + if err != nil { + return nil, err + } + + // Response struct + var requestResponse ProvisioningProfileListResponse + + // + // Perform request + response, _, err := RunRequest(client, request, &requestResponse) + if err != nil { + return nil, err + } + + requestResponse = *response.(*ProvisioningProfileListResponse) + return requestResponse.Data, nil +} + +// GetUploadedProvisioningProfileUUIDby ... +func (client *BitriseClient) GetUploadedProvisioningProfileUUIDby(profileSlug string) (UUID string, err error) { + downloadURL, err := client.getUploadedProvisioningProfileDownloadURLBy(profileSlug) + if err != nil { + return "", err + } + + content, err := client.downloadUploadedProvisioningProfile(downloadURL) + if err != nil { + return "", err + } + + plistData, err := profileutil.ProvisioningProfileFromContent([]byte(content)) + if err != nil { + return "", err + } + + data, err := profileutil.NewProvisioningProfileInfo(*plistData, profileutil.ProfileTypeIos) + if err != nil { + return "", err + } + + return data.UUID, nil +} + +func (client *BitriseClient) getUploadedProvisioningProfileDownloadURLBy(profileSlug string) (downloadURL string, err error) { + log.Debugf("\nGet downloadURL for provisioning profile (slug - %s) from Bitrise...", profileSlug) + + requestURL, err := urlutil.Join(baseURL, appsEndPoint, client.selectedAppSlug, provisioningProfilesEndPoint, profileSlug) + if err != nil { + return "", err + } + + log.Debugf("\nRequest URL: %s", requestURL) + + request, err := createRequest(http.MethodGet, requestURL, client.headers, nil) + if err != nil { + return "", err + } + + // Response struct + requestResponse := UploadedProvisioningProfileResponse{} + + // + // Perform request + response, _, err := RunRequest(client, request, &requestResponse) + if err != nil { + return "", err + } + + requestResponse = *response.(*UploadedProvisioningProfileResponse) + return requestResponse.Data.DownloadURL, nil +} + +func (client *BitriseClient) downloadUploadedProvisioningProfile(downloadURL string) (content string, err error) { + log.Debugf("\nDownloading provisioning profile from Bitrise...") + log.Debugf("\nRequest URL: %s", downloadURL) + + request, err := createRequest(http.MethodGet, downloadURL, nil, nil) + if err != nil { + return "", err + } + + // Response struct + var requestResponse string + + // + // Perform request + _, body, err := RunRequest(client, request, nil) + if err != nil { + return "", err + } + + requestResponse = string(body) + return requestResponse, nil + +} + +// RegisterProvisioningProfile ... +func (client *BitriseClient) RegisterProvisioningProfile(provisioningProfSize int64, profile profileutil.ProvisioningProfileInfoModel) (RegisterProvisioningProfileData, error) { + fmt.Println() + log.Infof("Register %s on Bitrise...", profile.Name) + + requestURL, err := urlutil.Join(baseURL, appsEndPoint, client.selectedAppSlug, provisioningProfilesEndPoint) + if err != nil { + return RegisterProvisioningProfileData{}, err + } + + log.Debugf("\nRequest URL: %s", requestURL) + + fields := map[string]interface{}{ + "upload_file_name": profile.Name, + "upload_file_size": provisioningProfSize, + } + + request, err := createRequest(http.MethodPost, requestURL, client.headers, fields) + if err != nil { + return RegisterProvisioningProfileData{}, err + } + + // Response struct + requestResponse := RegisterProvisioningProfileResponse{} + + // + // Perform request + response, _, err := RunRequest(client, request, &requestResponse) + if err != nil { + return RegisterProvisioningProfileData{}, err + } + + requestResponse = *response.(*RegisterProvisioningProfileResponse) + return requestResponse.Data, nil +} + +// UploadProvisioningProfile ... +func (client *BitriseClient) UploadProvisioningProfile(uploadURL string, uploadFileName string, outputDirPath string, exportFileName string) error { + fmt.Println() + log.Infof("Upload %s to Bitrise...", exportFileName) + + filePth := filepath.Join(outputDirPath, exportFileName) + + request, err := createUploadRequest(http.MethodPut, uploadURL, nil, filePth) + if err != nil { + return err + } + + // + // Perform request + _, _, err = RunRequest(client, request, nil) + if err != nil { + return err + } + + return nil +} + +// ConfirmProvisioningProfileUpload ... +func (client *BitriseClient) ConfirmProvisioningProfileUpload(profileSlug string, provUploadName string) error { + fmt.Println() + log.Infof("Confirm - %s - upload to Bitrise...", provUploadName) + + requestURL, err := urlutil.Join(baseURL, appsEndPoint, client.selectedAppSlug, provisioningProfilesEndPoint, profileSlug, "uploaded") + if err != nil { + return err + } + + request, err := createRequest("POST", requestURL, client.headers, nil) + if err != nil { + return err + } + + // Response struct + requestResponse := ConfirmProvProfileUploadResponse{} + + // + // Perform request + response, _, err := RunRequest(client, request, &requestResponse) + if err != nil { + return err + } + + requestResponse = *response.(*ConfirmProvProfileUploadResponse) + return nil +} diff --git a/cmd/common.go b/cmd/common.go index 2dbbfbb9..226f44b1 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -8,10 +8,13 @@ import ( "sort" "strings" + "github.com/bitrise-io/go-utils/colorstring" "github.com/bitrise-io/go-utils/command" "github.com/bitrise-io/go-utils/log" "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/sliceutil" "github.com/bitrise-io/goinp/goinp" + "github.com/bitrise-tools/codesigndoc/bitriseclient" "github.com/bitrise-tools/codesigndoc/osxkeychain" "github.com/bitrise-tools/go-xcode/certificateutil" "github.com/bitrise-tools/go-xcode/export" @@ -589,7 +592,6 @@ func exportCodesignFiles(tool Tool, archivePath, outputDirPath string) error { codeSignGroups := append(ipaExportCodeSignGroups, archiveCodeSignGroup) certificates, profiles := extractCertificatesAndProfiles(codeSignGroups...) - certificatesToExport = append(certificatesToExport, certificates...) profilesToExport = append(profilesToExport, profiles...) } @@ -602,15 +604,314 @@ func exportCodesignFiles(tool Tool, archivePath, outputDirPath string) error { return err } + provProfilesUploaded := (len(profilesToExport) == 0) + certsUploaded := (len(certificatesToExport) == 0) + + if len(profilesToExport) > 0 || len(certificatesToExport) > 0 { + fmt.Println() + shouldUpload, err := askUploadGeneratedFiles() + if err != nil { + return err + } + + if shouldUpload { + accessToken, err := getAccessToken() + if err != nil { + return err + } + + bitriseClient, appList, err := bitriseclient.NewBitriseClient(accessToken) + if err != nil { + return err + } + + selectedAppSlug, err := selectApp(appList) + if err != nil { + return err + } + + bitriseClient.SetSelectedAppSlug(selectedAppSlug) + + provProfilesUploaded, err = uploadExportedProvProfiles(bitriseClient, profilesToExport, outputDirPath) + if err != nil { + return err + } + + certsUploaded, err = uploadExportedIdentity(bitriseClient, certificatesToExport, outputDirPath) + if err != nil { + return err + } + } + } + fmt.Println() log.Successf("Exports finished you can find the exported files at: %s", outputDirPath) + if err := command.RunCommand("open", outputDirPath); err != nil { log.Errorf("Failed to open the export directory in Finder: %s", outputDirPath) } else { fmt.Println("Opened the directory in Finder.") } + printFinished(provProfilesUploaded, certsUploaded) + + return nil +} + +func getAccessToken() (string, error) { + accessToken, err := askAccessToken() + if err != nil { + return "", err + } + + return accessToken, nil +} + +func uploadExportedProvProfiles(bitriseClient *bitriseclient.BitriseClient, profilesToExport []profileutil.ProvisioningProfileInfoModel, outputDirPath string) (bool, error) { + profilesToUpload, err := filterAlreadyUploadedProvProfiles(bitriseClient, profilesToExport) + if err != nil { + return false, err + } + + if len(profilesToUpload) > 0 { + fmt.Println() + log.Infof("Uploading provisioning profiles...") + + if err := uploadProvisioningProfiles(bitriseClient, profilesToUpload, outputDirPath); err != nil { + return false, err + } + } + + return true, nil +} + +func uploadExportedIdentity(bitriseClient *bitriseclient.BitriseClient, certificatesToExport []certificateutil.CertificateInfoModel, outputDirPath string) (bool, error) { + shouldUploadIdentities, err := shouldUploadCertificates(bitriseClient, certificatesToExport) + if err != nil { + return false, err + } + + if shouldUploadIdentities { + fmt.Println() + log.Infof("Uploading certificate...") + + if err := UploadIdentity(bitriseClient, outputDirPath); err != nil { + return false, err + } + } else { + log.Warnf("There is no new certificate to upload...") + } + + return true, err +} + +func askUploadGeneratedFiles() (bool, error) { + messageToAsk := "Do you want to upload the provisioning profiles and certificates to Bitrise?" + return goinp.AskForBoolFromReader(messageToAsk, os.Stdin) +} + +func askUploadIdentities() (bool, error) { + messageToAsk := "Do you want to upload the certificates to Bitrise?" + return goinp.AskForBoolFromReader(messageToAsk, os.Stdin) +} + +func filterAlreadyUploadedProvProfiles(client *bitriseclient.BitriseClient, localProfiles []profileutil.ProvisioningProfileInfoModel) ([]profileutil.ProvisioningProfileInfoModel, error) { + fmt.Println() + log.Infof("Looking for provisioning profile duplicates on Bitrise...") + + uploadedProfileUUIDList := map[string]bool{} + profilesToUpload := []profileutil.ProvisioningProfileInfoModel{} + + uploadedProfInfoList, err := client.FetchProvisioningProfiles() + if err != nil { + return nil, err + } + + for _, uploadedProfileInfo := range uploadedProfInfoList { + uploadedProfileUUID, err := client.GetUploadedProvisioningProfileUUIDby(uploadedProfileInfo.Slug) + if err != nil { + return nil, err + } + + uploadedProfileUUIDList[uploadedProfileUUID] = true + } + + for _, localProfile := range localProfiles { + contains, _ := uploadedProfileUUIDList[localProfile.UUID] + if contains { + log.Warnf("Already on Bitrise: - %s - (UUID: %s) ", localProfile.Name, localProfile.UUID) + } else { + profilesToUpload = append(profilesToUpload, localProfile) + } + } + + return profilesToUpload, nil +} + +func shouldUploadCertificates(client *bitriseclient.BitriseClient, certificatesToExport []certificateutil.CertificateInfoModel) (bool, error) { + fmt.Println() + log.Infof("Looking for certificate duplicates on Bitrise...") + + var uploadedCertificatesSerialList []string + localCertificatesSerialList := []string{} + + uploadedItentityList, err := client.FetchUploadedIdentities() + if err != nil { + return false, err + } + + // Get uploaded certificates' serials + for _, uploadedIdentity := range uploadedItentityList { + var serialListAsString []string + + serialList, err := client.GetUploadedCertificatesSerialby(uploadedIdentity.Slug) + if err != nil { + return false, err + } + + for _, serial := range serialList { + serialListAsString = append(serialListAsString, serial.String()) + } + uploadedCertificatesSerialList = append(uploadedCertificatesSerialList, serialListAsString...) + } + + for _, certificateToExport := range certificatesToExport { + localCertificatesSerialList = append(localCertificatesSerialList, certificateToExport.Serial) + } + + log.Debugf("Uploaded certificates' serial list: \n\t%v", uploadedCertificatesSerialList) + log.Debugf("Local certificates' serial list: \n\t%v", localCertificatesSerialList) + + // Search for a new certificate + for _, localCertificateSerial := range localCertificatesSerialList { + if !sliceutil.IsStringInSlice(localCertificateSerial, uploadedCertificatesSerialList) { + return true, nil + } + } + + return false, nil +} + +// ---------------------------------------------------------------- +// --- Upload methods +func uploadProvisioningProfiles(bitriseClient *bitriseclient.BitriseClient, profilesToUpload []profileutil.ProvisioningProfileInfoModel, outputDirPath string) error { + for _, profile := range profilesToUpload { + exportFileName := provProfileExportFileName(profile, outputDirPath) + + provProfile, err := os.Open(outputDirPath + "/" + exportFileName) + if err != nil { + return err + } + + defer func() { + if err := provProfile.Close(); err != nil { + log.Warnf("Provisioning profile close failed, err: %s", err) + } + + }() - printFinished(certificatesOnly) + info, err := provProfile.Stat() + if err != nil { + return err + } + + log.Debugf("\n%s size: %d", exportFileName, info.Size()) + + provProfSlugResponseData, err := bitriseClient.RegisterProvisioningProfile(info.Size(), profile) + if err != nil { + return err + } + + if err := bitriseClient.UploadProvisioningProfile(provProfSlugResponseData.UploadURL, provProfSlugResponseData.UploadFileName, outputDirPath, exportFileName); err != nil { + return err + } + + if err := bitriseClient.ConfirmProvisioningProfileUpload(provProfSlugResponseData.Slug, provProfSlugResponseData.UploadFileName); err != nil { + return err + } + } return nil } + +// UploadIdentity ... +func UploadIdentity(bitriseClient *bitriseclient.BitriseClient, outputDirPath string) error { + identities, err := os.Open(outputDirPath + "/" + "Identities.p12") + if err != nil { + return err + } + + defer func() { + if err := identities.Close(); err != nil { + log.Warnf("Identities failed, err: %s", err) + } + + }() + + info, err := identities.Stat() + if err != nil { + return err + } + + log.Debugf("\n%s size: %d", "Identities.p12", info.Size()) + + certificateResponseData, err := bitriseClient.RegisterIdentity(info.Size()) + if err != nil { + return err + } + + if err := bitriseClient.UploadIdentity(certificateResponseData.UploadURL, certificateResponseData.UploadFileName, outputDirPath, "Identities.p12"); err != nil { + return err + } + + return bitriseClient.ConfirmIdentityUpload(certificateResponseData.Slug, certificateResponseData.UploadFileName) +} + +func askAccessToken() (token string, err error) { + messageToAsk := `Please copy your personal access token to Bitrise. +(To acquire a Personal Access Token for your user, sign in with that user on bitrise.io, go to your Account Settings page, +and select the Security tab on the left side.)` + fmt.Println() + + accesToken, err := goinp.AskForStringFromReader(messageToAsk, os.Stdin) + if err != nil { + return accesToken, err + } + + fmt.Println() + log.Infof("%s %s", colorstring.Green("Given accesToken:"), accesToken) + fmt.Println() + + return accesToken, nil +} + +func selectApp(appList []bitriseclient.Application) (seledtedAppSlug string, err error) { + var selectionList []string + + for _, app := range appList { + selectionList = append(selectionList, app.Title+" ("+app.RepoURL+")") + } + userSelection, err := goinp.SelectFromStringsWithDefault("Select the app which you want to upload the privisioning profiles", 1, selectionList) + + if err != nil { + return "", fmt.Errorf("failed to read input: %s", err) + + } + + log.Debugf("selected app: %v", userSelection) + + for index, selected := range selectionList { + if selected == userSelection { + return appList[index].Slug, nil + } + } + + return "", &appSelectionError{"failed to find selected app in appList"} +} + +type appSelectionError struct { + s string +} + +func (e *appSelectionError) Error() string { + return e.s +} diff --git a/cmd/print.go b/cmd/print.go index bd536d10..1c9fb7c0 100644 --- a/cmd/print.go +++ b/cmd/print.go @@ -9,15 +9,14 @@ import ( "github.com/bitrise-tools/go-xcode/export" ) -func printFinished(certsOnly bool) { +func printFinished(provProfilesUploaded bool, certsUploaded bool) { fmt.Println() log.Successf("That's all.") - if certsOnly { - log.Warnf("You just have to upload the found certificates (.p12) and you'll be good to go!") - } else { + + if !provProfilesUploaded && !certsUploaded { log.Warnf("You just have to upload the found certificates (.p12) and provisioning profiles (.mobileprovision) and you'll be good to go!") + fmt.Println() } - fmt.Println() } func printCodesignGroup(group export.IosCodeSignGroup) {