diff --git a/exportoptionsgenerator/codesign_group_provider.go b/exportoptionsgenerator/codesign_group_provider.go index 60f95086..2ef05ee5 100644 --- a/exportoptionsgenerator/codesign_group_provider.go +++ b/exportoptionsgenerator/codesign_group_provider.go @@ -3,28 +3,32 @@ package exportoptionsgenerator import ( "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-xcode/certificateutil" - "github.com/bitrise-io/go-xcode/export" "github.com/bitrise-io/go-xcode/exportoptions" "github.com/bitrise-io/go-xcode/profileutil" + codesigngroup "github.com/bitrise-io/go-xcode/v2/exportoptionsgenerator/internal/codesigngroup" "github.com/bitrise-io/go-xcode/v2/plistutil" ) // CodeSignGroupProvider ... type CodeSignGroupProvider interface { - DetermineCodesignGroup(certificates []certificateutil.CertificateInfoModel, profiles []profileutil.ProvisioningProfileInfoModel, defaultProfile *profileutil.ProvisioningProfileInfoModel, bundleIDEntitlementsMap map[string]plistutil.PlistData, exportMethod exportoptions.Method, teamID string, xcodeManaged bool) (*export.IosCodeSignGroup, error) + DetermineCodesignGroup(certificates []certificateutil.CertificateInfoModel, profiles []profileutil.ProvisioningProfileInfoModel, defaultProfile *profileutil.ProvisioningProfileInfoModel, bundleIDEntitlementsMap map[string]plistutil.PlistData, exportMethod exportoptions.Method, teamID string, xcodeManaged bool) (*codesigngroup.Ios, error) } type codeSignGroupProvider struct { - logger log.Logger + logger log.Logger + printer *codesigngroup.Printer } // NewCodeSignGroupProvider ... func NewCodeSignGroupProvider(logger log.Logger) CodeSignGroupProvider { - return &codeSignGroupProvider{logger: logger} + return &codeSignGroupProvider{ + logger: logger, + printer: codesigngroup.NewPrinter(logger), + } } // DetermineCodesignGroup .... -func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificateutil.CertificateInfoModel, profiles []profileutil.ProvisioningProfileInfoModel, defaultProfile *profileutil.ProvisioningProfileInfoModel, bundleIDEntitlementsMap map[string]plistutil.PlistData, exportMethod exportoptions.Method, teamID string, xcodeManaged bool) (*export.IosCodeSignGroup, error) { +func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificateutil.CertificateInfoModel, profiles []profileutil.ProvisioningProfileInfoModel, defaultProfile *profileutil.ProvisioningProfileInfoModel, bundleIDEntitlementsMap map[string]plistutil.PlistData, exportMethod exportoptions.Method, teamID string, xcodeManaged bool) (*codesigngroup.Ios, error) { g.logger.Println() g.logger.Printf("Target Bundle ID - Entitlements map") var bundleIDs []string @@ -38,9 +42,6 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate g.logger.Printf("%s: %s", bundleID, entitlementKeys) } - g.logger.Println() - g.logger.Printf("Resolving CodeSignGroups...") - g.logger.Debugf("Installed certificates:") for _, certInfo := range certificates { g.logger.Debugf(certInfo.String()) @@ -51,76 +52,65 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate g.logger.Debugf(profileInfo.String(certificates...)) } - g.logger.Printf("Resolving CodeSignGroups...") - codeSignGroups := export.CreateSelectableCodeSignGroups(certificates, profiles, bundleIDs) + g.logger.Println() + g.logger.Printf("Resolving code signing groups...") + codeSignGroups := codesigngroup.BuildFilterableList(certificates, profiles, bundleIDs) if len(codeSignGroups) == 0 { g.logger.Errorf("Failed to find code signing groups for specified export method (%s)", exportMethod) } g.logger.Debugf("\nGroups:") - for _, group := range codeSignGroups { - g.logger.Debugf(group.String()) - } + g.logger.Debugf("%s", g.printer.ListToDebugString(codeSignGroups)) if len(bundleIDEntitlementsMap) > 0 { - g.logger.Warnf("Filtering CodeSignInfo groups for target capabilities") + g.logger.Printf("Filtering code signing groups for target capabilities") - codeSignGroups = export.FilterSelectableCodeSignGroups(codeSignGroups, export.CreateEntitlementsSelectableCodeSignGroupFilter(convertToV1PlistData(bundleIDEntitlementsMap))) + codeSignGroups = codesigngroup.Filter(codeSignGroups, codesigngroup.CreateEntitlementsSelectableCodeSignGroupFilter(convertToV1PlistData(bundleIDEntitlementsMap))) g.logger.Debugf("\nGroups after filtering for target capabilities:") - for _, group := range codeSignGroups { - g.logger.Debugf(group.String()) - } + g.logger.Debugf("%s", g.printer.ListToDebugString(codeSignGroups)) } - g.logger.Warnf("Filtering CodeSignInfo groups for export method") + g.logger.Printf("Filtering code signing groups for export method %s", exportMethod) - codeSignGroups = export.FilterSelectableCodeSignGroups(codeSignGroups, export.CreateExportMethodSelectableCodeSignGroupFilter(exportMethod)) + codeSignGroups = codesigngroup.Filter(codeSignGroups, codesigngroup.CreateExportMethodSelectableCodeSignGroupFilter(exportMethod)) g.logger.Debugf("\nGroups after filtering for export method:") - for _, group := range codeSignGroups { - g.logger.Debugf(group.String()) - } + g.logger.Debugf("%s", g.printer.ListToDebugString(codeSignGroups)) if teamID != "" { - g.logger.Warnf("ExportDevelopmentTeam specified: %s, filtering CodeSignInfo groups...", teamID) + g.logger.Printf("Development team specified: %s, filtering groups...", teamID) - codeSignGroups = export.FilterSelectableCodeSignGroups(codeSignGroups, export.CreateTeamSelectableCodeSignGroupFilter(teamID)) + codeSignGroups = codesigngroup.Filter(codeSignGroups, codesigngroup.CreateTeamSelectableCodeSignGroupFilter(teamID)) g.logger.Debugf("\nGroups after filtering for team ID:") - for _, group := range codeSignGroups { - g.logger.Debugf(group.String()) - } + g.logger.Debugf("%s", g.printer.ListToDebugString(codeSignGroups)) } if !xcodeManaged { - g.logger.Warnf("App was signed with NON Xcode managed profile when archiving,\n" + - "only NOT Xcode managed profiles are allowed to sign when exporting the archive.\n" + - "Removing Xcode managed CodeSignInfo groups") + g.logger.Printf("App was signed with NON Xcode managed profile when archiving,\n" + + "only NON Xcode managed profiles are allowed to sign when exporting the archive.\n" + + "Removing Xcode managed code signing groups") - codeSignGroups = export.FilterSelectableCodeSignGroups(codeSignGroups, export.CreateNotXcodeManagedSelectableCodeSignGroupFilter()) + codeSignGroups = codesigngroup.Filter(codeSignGroups, codesigngroup.CreateNotXcodeManagedSelectableCodeSignGroupFilter()) - g.logger.Debugf("\nGroups after filtering for NOT Xcode managed profiles:") - for _, group := range codeSignGroups { - g.logger.Debugf(group.String()) - } + g.logger.Debugf("\nGroups after filtering for NON Xcode managed profiles:") + g.logger.Debugf("%s", g.printer.ListToDebugString(codeSignGroups)) } if teamID == "" && defaultProfile != nil { g.logger.Debugf("\ndefault profile: %v\n", defaultProfile) - filteredCodeSignGroups := export.FilterSelectableCodeSignGroups(codeSignGroups, - export.CreateExcludeProfileNameSelectableCodeSignGroupFilter(defaultProfile.Name)) + filteredCodeSignGroups := codesigngroup.Filter(codeSignGroups, + codesigngroup.CreateExcludeProfileNameSelectableCodeSignGroupFilter(defaultProfile.Name)) if len(filteredCodeSignGroups) > 0 { codeSignGroups = filteredCodeSignGroups - + g.logger.Printf("Removed default profile '%s' from code signing groups", defaultProfile.Name) g.logger.Debugf("\nGroups after removing default profile:") - for _, group := range codeSignGroups { - g.logger.Debugf(group.String()) - } + g.logger.Debugf("%s", g.printer.ListToDebugString(codeSignGroups)) } } - var iosCodeSignGroups []export.IosCodeSignGroup + var iosCodeSignGroups []codesigngroup.Ios for _, selectable := range codeSignGroups { bundleIDProfileMap := map[string]profileutil.ProvisioningProfileInfoModel{} @@ -132,7 +122,7 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate } } - iosCodeSignGroups = append(iosCodeSignGroups, *export.NewIOSGroup(selectable.Certificate, bundleIDProfileMap)) + iosCodeSignGroups = append(iosCodeSignGroups, *codesigngroup.NewIOSGroup(selectable.Certificate, bundleIDProfileMap)) } g.logger.Debugf("\nFiltered groups:") @@ -144,12 +134,12 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate } if len(iosCodeSignGroups) < 1 { - g.logger.Errorf("Failed to find Codesign Groups") + g.logger.Errorf("Failed to find code signing groups") return nil, nil } if len(iosCodeSignGroups) > 1 { - g.logger.Warnf("Multiple code signing groups found! Using the first code signing group") + g.logger.Warnf("Multiple code signing groups found! Using the first code signing group.") } return &iosCodeSignGroups[0], nil diff --git a/exportoptionsgenerator/exportoptionsgenerator.go b/exportoptionsgenerator/exportoptionsgenerator.go index 872d23a5..e17f70c2 100644 --- a/exportoptionsgenerator/exportoptionsgenerator.go +++ b/exportoptionsgenerator/exportoptionsgenerator.go @@ -5,9 +5,9 @@ import ( "slices" "github.com/bitrise-io/go-utils/v2/log" - "github.com/bitrise-io/go-xcode/export" "github.com/bitrise-io/go-xcode/exportoptions" "github.com/bitrise-io/go-xcode/profileutil" + "github.com/bitrise-io/go-xcode/v2/exportoptionsgenerator/internal/codesigngroup" "github.com/bitrise-io/go-xcode/v2/plistutil" "github.com/bitrise-io/go-xcode/v2/xcodeversion" ) @@ -128,7 +128,7 @@ func (g ExportOptionsGenerator) GenerateApplicationExportOptions( // determineCodesignGroup finds the best codesign group (certificate + profiles) // based on the installed Provisioning Profiles and Codesign Certificates. -func (g ExportOptionsGenerator) determineCodesignGroup(bundleIDEntitlementsMap map[string]plistutil.PlistData, exportMethod exportoptions.Method, teamID string, xcodeManaged bool) (*export.IosCodeSignGroup, error) { +func (g ExportOptionsGenerator) determineCodesignGroup(bundleIDEntitlementsMap map[string]plistutil.PlistData, exportMethod exportoptions.Method, teamID string, xcodeManaged bool) (*codesigngroup.Ios, error) { certs, err := g.certificateProvider.ListCodesignIdentities() if err != nil { return nil, fmt.Errorf("failed to get installed certificates: %w", err) @@ -268,7 +268,7 @@ func addTestFlightInternalTestingOnly(exportOpts exportoptions.ExportOptions, te return exportOpts } -func addManualSigningFields(exportOpts exportoptions.ExportOptions, codeSignGroup *export.IosCodeSignGroup, archivedWithXcodeManagedProfiles bool, logger log.Logger) exportoptions.ExportOptions { +func addManualSigningFields(exportOpts exportoptions.ExportOptions, codeSignGroup *codesigngroup.Ios, archivedWithXcodeManagedProfiles bool, logger log.Logger) exportoptions.ExportOptions { exportCodeSignStyle := "" exportProfileMapping := map[string]string{} diff --git a/exportoptionsgenerator/internal/codesigngroup/codesigngroup.go b/exportoptionsgenerator/internal/codesigngroup/codesigngroup.go new file mode 100644 index 00000000..26b8fd61 --- /dev/null +++ b/exportoptionsgenerator/internal/codesigngroup/codesigngroup.go @@ -0,0 +1,104 @@ +package codesigngroup + +import ( + "sort" + + "github.com/bitrise-io/go-xcode/certificateutil" + "github.com/bitrise-io/go-xcode/profileutil" + "github.com/ryanuber/go-glob" +) + +// CodeSignGroup ... +type CodeSignGroup interface { + Certificate() certificateutil.CertificateInfoModel + InstallerCertificate() *certificateutil.CertificateInfoModel + BundleIDProfileMap() map[string]profileutil.ProvisioningProfileInfoModel +} + +// SelectableCodeSignGroup ... +type SelectableCodeSignGroup struct { + Certificate certificateutil.CertificateInfoModel + BundleIDProfilesMap map[string][]profileutil.ProvisioningProfileInfoModel +} + +func containsCertificate(installedCertificates []certificateutil.CertificateInfoModel, certificate certificateutil.CertificateInfoModel) bool { + for _, cert := range installedCertificates { + if cert.Serial == certificate.Serial { + return true + } + } + return false +} + +// BuildFilterableList ... +func BuildFilterableList(installedCertificates []certificateutil.CertificateInfoModel, profiles []profileutil.ProvisioningProfileInfoModel, bundleIDs []string) []SelectableCodeSignGroup { + groups := []SelectableCodeSignGroup{} + + serialToProfiles := map[string][]profileutil.ProvisioningProfileInfoModel{} + serialToCertificate := map[string]certificateutil.CertificateInfoModel{} + for _, profile := range profiles { + for _, certificate := range profile.DeveloperCertificates { + if !containsCertificate(installedCertificates, certificate) { + continue + } + + certificateProfiles, ok := serialToProfiles[certificate.Serial] + if !ok { + certificateProfiles = []profileutil.ProvisioningProfileInfoModel{} + } + certificateProfiles = append(certificateProfiles, profile) + serialToProfiles[certificate.Serial] = certificateProfiles + serialToCertificate[certificate.Serial] = certificate + } + } + + for serial, profiles := range serialToProfiles { + certificate := serialToCertificate[serial] + + bundleIDToProfiles := map[string][]profileutil.ProvisioningProfileInfoModel{} + for _, bundleID := range bundleIDs { + + matchingProfiles := []profileutil.ProvisioningProfileInfoModel{} + for _, profile := range profiles { + if !glob.Glob(profile.BundleID, bundleID) { + continue + } + + matchingProfiles = append(matchingProfiles, profile) + } + + if len(matchingProfiles) > 0 { + sort.Sort(ByBundleIDLength(matchingProfiles)) + bundleIDToProfiles[bundleID] = matchingProfiles + } + } + + if len(bundleIDToProfiles) == len(bundleIDs) { + group := SelectableCodeSignGroup{ + Certificate: certificate, + BundleIDProfilesMap: bundleIDToProfiles, + } + groups = append(groups, group) + } + } + + return groups +} + +// ByBundleIDLength ... +type ByBundleIDLength []profileutil.ProvisioningProfileInfoModel + +// Len .. +func (s ByBundleIDLength) Len() int { + return len(s) +} + +// Swap ... +func (s ByBundleIDLength) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +// Less ... +func (s ByBundleIDLength) Less(i, j int) bool { + return len(s[i].BundleID) > len(s[j].BundleID) +} diff --git a/exportoptionsgenerator/internal/codesigngroup/codesigngroup_test.go b/exportoptionsgenerator/internal/codesigngroup/codesigngroup_test.go new file mode 100644 index 00000000..e4afa387 --- /dev/null +++ b/exportoptionsgenerator/internal/codesigngroup/codesigngroup_test.go @@ -0,0 +1,107 @@ +package codesigngroup_test + +import ( + "testing" + + "github.com/bitrise-io/go-xcode/certificateutil" + "github.com/bitrise-io/go-xcode/exportoptions" + "github.com/bitrise-io/go-xcode/profileutil" + "github.com/bitrise-io/go-xcode/v2/exportoptionsgenerator/internal/codesigngroup" + "github.com/stretchr/testify/require" +) + +func TestCreateSelectableCodeSignGroups(t *testing.T) { + certDev := certificateutil.CertificateInfoModel{ + CommonName: "iPhone Distribution: Bitrise Test (ABCD1234)", + TeamID: "ABCD1234", + } + profileDev := profileutil.ProvisioningProfileInfoModel{ + Name: "Bitrise Test Profile", + UUID: "PROFILE-UUID-1234", + TeamID: "ABCD1234", + BundleID: "io.bitrise.testapp", + ExportType: exportoptions.MethodAppStore, + DeveloperCertificates: []certificateutil.CertificateInfoModel{certDev}, + } + + tests := []struct { + name string + certificates []certificateutil.CertificateInfoModel + profiles []profileutil.ProvisioningProfileInfoModel + bundleIDs []string + filter codesigngroup.SelectableCodeSignGroupFilter + want []codesigngroup.SelectableCodeSignGroup + }{ + { + name: "empty inputs", + certificates: []certificateutil.CertificateInfoModel{}, + profiles: []profileutil.ProvisioningProfileInfoModel{}, + bundleIDs: []string{}, + want: []codesigngroup.SelectableCodeSignGroup{}, + }, + { + name: "single matching profile and certificate", + certificates: []certificateutil.CertificateInfoModel{certDev}, + profiles: []profileutil.ProvisioningProfileInfoModel{profileDev}, + bundleIDs: []string{"io.bitrise.testapp"}, + want: []codesigngroup.SelectableCodeSignGroup{ + { + Certificate: certDev, + BundleIDProfilesMap: map[string][]profileutil.ProvisioningProfileInfoModel{ + "io.bitrise.testapp": {profileDev}, + }, + }, + }, + }, + { + name: "filter by team ID, no match", + certificates: []certificateutil.CertificateInfoModel{ + certDev, + }, + profiles: []profileutil.ProvisioningProfileInfoModel{ + profileDev, + }, + bundleIDs: []string{"io.bitrise.testapp"}, + filter: codesigngroup.CreateTeamSelectableCodeSignGroupFilter("WRONGID"), + want: []codesigngroup.SelectableCodeSignGroup{}, + }, + { + name: "filter by team ID, match", + certificates: []certificateutil.CertificateInfoModel{ + certDev, + }, + profiles: []profileutil.ProvisioningProfileInfoModel{ + profileDev, + }, + bundleIDs: []string{"io.bitrise.testapp"}, + filter: codesigngroup.CreateTeamSelectableCodeSignGroupFilter("ABCD1234"), + want: []codesigngroup.SelectableCodeSignGroup{ + { + Certificate: certDev, + BundleIDProfilesMap: map[string][]profileutil.ProvisioningProfileInfoModel{ + "io.bitrise.testapp": {profileDev}, + }, + }, + }, + }, + { + name: "filter out app store distribution", + certificates: []certificateutil.CertificateInfoModel{ + certDev, + }, + profiles: []profileutil.ProvisioningProfileInfoModel{ + profileDev, + }, + bundleIDs: []string{"io.bitrise.testapp"}, + filter: codesigngroup.CreateExportMethodSelectableCodeSignGroupFilter(exportoptions.MethodAdHoc), + want: []codesigngroup.SelectableCodeSignGroup{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := codesigngroup.BuildFilterableList(tt.certificates, tt.profiles, tt.bundleIDs) + got = codesigngroup.Filter(got, tt.filter) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/exportoptionsgenerator/internal/codesigngroup/filter.go b/exportoptionsgenerator/internal/codesigngroup/filter.go new file mode 100644 index 00000000..bdd9bb14 --- /dev/null +++ b/exportoptionsgenerator/internal/codesigngroup/filter.go @@ -0,0 +1,184 @@ +package codesigngroup + +import ( + "github.com/bitrise-io/go-xcode/exportoptions" + "github.com/bitrise-io/go-xcode/plistutil" + "github.com/bitrise-io/go-xcode/profileutil" +) + +// SelectableCodeSignGroupFilter ... +type SelectableCodeSignGroupFilter func(group *SelectableCodeSignGroup) bool + +// Filter ... +func Filter(groups []SelectableCodeSignGroup, filterFunc SelectableCodeSignGroupFilter) []SelectableCodeSignGroup { + if filterFunc == nil { + return groups + } + + filteredGroups := []SelectableCodeSignGroup{} + for _, group := range groups { + if filterFunc(&group) { + filteredGroups = append(filteredGroups, group) + } + } + + return filteredGroups +} + +// CreateEntitlementsSelectableCodeSignGroupFilter ... +func CreateEntitlementsSelectableCodeSignGroupFilter(bundleIDEntitlementsMap map[string]plistutil.PlistData) SelectableCodeSignGroupFilter { + return func(group *SelectableCodeSignGroup) bool { + filteredBundleIDProfilesMap := map[string][]profileutil.ProvisioningProfileInfoModel{} + + for bundleID, profiles := range group.BundleIDProfilesMap { + filteredProfiles := []profileutil.ProvisioningProfileInfoModel{} + + for _, profile := range profiles { + missingEntitlements := profileutil.MatchTargetAndProfileEntitlements(bundleIDEntitlementsMap[bundleID], profile.Entitlements, profile.Type) + if len(missingEntitlements) == 0 { + filteredProfiles = append(filteredProfiles, profile) + } + } + + if len(filteredProfiles) == 0 { + break + } + + filteredBundleIDProfilesMap[bundleID] = filteredProfiles + } + + if len(filteredBundleIDProfilesMap) == len(group.BundleIDProfilesMap) { + group.BundleIDProfilesMap = filteredBundleIDProfilesMap + return true + } + + return false + } +} + +// CreateExportMethodSelectableCodeSignGroupFilter ... +func CreateExportMethodSelectableCodeSignGroupFilter(exportMethod exportoptions.Method) SelectableCodeSignGroupFilter { + return func(group *SelectableCodeSignGroup) bool { + filteredBundleIDProfilesMap := map[string][]profileutil.ProvisioningProfileInfoModel{} + + for bundleID, profiles := range group.BundleIDProfilesMap { + filteredProfiles := []profileutil.ProvisioningProfileInfoModel{} + + for _, profile := range profiles { + if profile.ExportType == exportMethod { + filteredProfiles = append(filteredProfiles, profile) + } + } + + if len(filteredProfiles) == 0 { + break + } + + filteredBundleIDProfilesMap[bundleID] = filteredProfiles + } + + if len(filteredBundleIDProfilesMap) == len(group.BundleIDProfilesMap) { + group.BundleIDProfilesMap = filteredBundleIDProfilesMap + return true + } + + return false + } +} + +// CreateTeamSelectableCodeSignGroupFilter ... +func CreateTeamSelectableCodeSignGroupFilter(teamID string) SelectableCodeSignGroupFilter { + return func(group *SelectableCodeSignGroup) bool { + return group.Certificate.TeamID == teamID + } +} + +// CreateNotXcodeManagedSelectableCodeSignGroupFilter ... +func CreateNotXcodeManagedSelectableCodeSignGroupFilter() SelectableCodeSignGroupFilter { + return func(group *SelectableCodeSignGroup) bool { + filteredBundleIDProfilesMap := map[string][]profileutil.ProvisioningProfileInfoModel{} + + for bundleID, profiles := range group.BundleIDProfilesMap { + filteredProfiles := []profileutil.ProvisioningProfileInfoModel{} + + for _, profile := range profiles { + if !profile.IsXcodeManaged() { + filteredProfiles = append(filteredProfiles, profile) + } + } + + if len(filteredProfiles) == 0 { + break + } + + filteredBundleIDProfilesMap[bundleID] = filteredProfiles + } + + if len(filteredBundleIDProfilesMap) == len(group.BundleIDProfilesMap) { + group.BundleIDProfilesMap = filteredBundleIDProfilesMap + return true + } + + return false + } +} + +// CreateXcodeManagedSelectableCodeSignGroupFilter ... +func CreateXcodeManagedSelectableCodeSignGroupFilter() SelectableCodeSignGroupFilter { + return func(group *SelectableCodeSignGroup) bool { + filteredBundleIDProfilesMap := map[string][]profileutil.ProvisioningProfileInfoModel{} + + for bundleID, profiles := range group.BundleIDProfilesMap { + filteredProfiles := []profileutil.ProvisioningProfileInfoModel{} + + for _, profile := range profiles { + if profile.IsXcodeManaged() { + filteredProfiles = append(filteredProfiles, profile) + } + } + + if len(filteredProfiles) == 0 { + break + } + + filteredBundleIDProfilesMap[bundleID] = filteredProfiles + } + + if len(filteredBundleIDProfilesMap) == len(group.BundleIDProfilesMap) { + group.BundleIDProfilesMap = filteredBundleIDProfilesMap + return true + } + + return false + } +} + +// CreateExcludeProfileNameSelectableCodeSignGroupFilter ... +func CreateExcludeProfileNameSelectableCodeSignGroupFilter(name string) SelectableCodeSignGroupFilter { + return func(group *SelectableCodeSignGroup) bool { + filteredBundleIDProfilesMap := map[string][]profileutil.ProvisioningProfileInfoModel{} + + for bundleID, profiles := range group.BundleIDProfilesMap { + filteredProfiles := []profileutil.ProvisioningProfileInfoModel{} + + for _, profile := range profiles { + if profile.Name != name { + filteredProfiles = append(filteredProfiles, profile) + } + } + + if len(filteredProfiles) == 0 { + break + } + + filteredBundleIDProfilesMap[bundleID] = filteredProfiles + } + + if len(filteredBundleIDProfilesMap) == len(group.BundleIDProfilesMap) { + group.BundleIDProfilesMap = filteredBundleIDProfilesMap + return true + } + + return false + } +} diff --git a/exportoptionsgenerator/internal/codesigngroup/ios.go b/exportoptionsgenerator/internal/codesigngroup/ios.go new file mode 100644 index 00000000..3c4fefbf --- /dev/null +++ b/exportoptionsgenerator/internal/codesigngroup/ios.go @@ -0,0 +1,35 @@ +package codesigngroup + +import ( + "github.com/bitrise-io/go-xcode/certificateutil" + "github.com/bitrise-io/go-xcode/profileutil" +) + +// Ios ... +type Ios struct { + certificate certificateutil.CertificateInfoModel + bundleIDProfileMap map[string]profileutil.ProvisioningProfileInfoModel +} + +// Certificate ... +func (signGroup *Ios) Certificate() certificateutil.CertificateInfoModel { + return signGroup.certificate +} + +// InstallerCertificate ... +func (signGroup *Ios) InstallerCertificate() *certificateutil.CertificateInfoModel { + return nil +} + +// BundleIDProfileMap ... +func (signGroup *Ios) BundleIDProfileMap() map[string]profileutil.ProvisioningProfileInfoModel { + return signGroup.bundleIDProfileMap +} + +// NewIOSGroup ... +func NewIOSGroup(certificate certificateutil.CertificateInfoModel, bundleIDProfileMap map[string]profileutil.ProvisioningProfileInfoModel) *Ios { + return &Ios{ + certificate: certificate, + bundleIDProfileMap: bundleIDProfileMap, + } +} diff --git a/exportoptionsgenerator/internal/codesigngroup/printer.go b/exportoptionsgenerator/internal/codesigngroup/printer.go new file mode 100644 index 00000000..58ffa6da --- /dev/null +++ b/exportoptionsgenerator/internal/codesigngroup/printer.go @@ -0,0 +1,56 @@ +package codesigngroup + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/bitrise-io/go-utils/v2/log" +) + +// Printer ... +type Printer struct { + logger log.Logger +} + +// NewPrinter ... +func NewPrinter(logger log.Logger) *Printer { + return &Printer{ + logger: logger, + } +} + +// ListToDebugString ... +func (printer *Printer) ListToDebugString(groups []SelectableCodeSignGroup) string { + var builder strings.Builder + for _, group := range groups { + builder.WriteString(printer.ToDebugString(group) + "\n") + } + + return builder.String() +} + +// ToDebugString ... +func (printer *Printer) ToDebugString(group SelectableCodeSignGroup) string { + printable := map[string]any{} + printable["team"] = fmt.Sprintf("%s (%s)", group.Certificate.TeamName, group.Certificate.TeamID) + printable["certificate"] = fmt.Sprintf("%s (%s)", group.Certificate.CommonName, group.Certificate.Serial) + + bundleIDProfiles := map[string][]string{} + for bundleID, profileInfos := range group.BundleIDProfilesMap { + printableProfiles := []string{} + for _, profileInfo := range profileInfos { + printableProfiles = append(printableProfiles, fmt.Sprintf("%s (%s)", profileInfo.Name, profileInfo.UUID)) + } + bundleIDProfiles[bundleID] = printableProfiles + } + printable["bundle_id_profiles"] = bundleIDProfiles + + data, err := json.MarshalIndent(printable, "", "\t") + if err != nil { + printer.logger.Errorf("Failed to marshal (%v): %s", printable, err) + return "" + } + + return string(data) +} diff --git a/exportoptionsgenerator/internal/codesigngroup/printer_test.go b/exportoptionsgenerator/internal/codesigngroup/printer_test.go new file mode 100644 index 00000000..0e152cc1 --- /dev/null +++ b/exportoptionsgenerator/internal/codesigngroup/printer_test.go @@ -0,0 +1,67 @@ +package codesigngroup_test + +import ( + "testing" + + "github.com/bitrise-io/go-utils/v2/log" + "github.com/bitrise-io/go-xcode/certificateutil" + "github.com/bitrise-io/go-xcode/profileutil" + "github.com/bitrise-io/go-xcode/v2/exportoptionsgenerator/internal/codesigngroup" + "github.com/stretchr/testify/require" +) + +func TestPrinter_ToDebugString(t *testing.T) { + tests := []struct { + name string + group codesigngroup.SelectableCodeSignGroup + want string + }{ + { + name: "empty group", + group: codesigngroup.SelectableCodeSignGroup{ + Certificate: certificateutil.CertificateInfoModel{ + CommonName: "CN", + Serial: "SERIAL", + TeamID: "TEAMID", + }, + BundleIDProfilesMap: map[string][]profileutil.ProvisioningProfileInfoModel{ + "com.example.app": { + { + Name: "Profile 1", + UUID: "UUID1", + }, + { + Name: "Profile 2", + UUID: "UUID2", + }, + }, + "com.example.appext": {{ + Name: "Profile 3", + UUID: "UUID3", + }}, + }, + }, + want: `{ + "bundle_id_profiles": { + "com.example.app": [ + "Profile 1 (UUID1)", + "Profile 2 (UUID2)" + ], + "com.example.appext": [ + "Profile 3 (UUID3)" + ] + }, + "certificate": "CN (SERIAL)", + "team": " (TEAMID)" +}`, + }, + } + logger := log.NewLogger() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + printer := codesigngroup.NewPrinter(logger) + got := printer.ToDebugString(tt.group) + require.JSONEq(t, tt.want, got) + }) + } +}