diff --git a/autocodesign/devportalclient/appstoreconnectclient/profiles.go b/autocodesign/devportalclient/appstoreconnectclient/profiles.go index 46ece7da..32683e04 100644 --- a/autocodesign/devportalclient/appstoreconnectclient/profiles.go +++ b/autocodesign/devportalclient/appstoreconnectclient/profiles.go @@ -399,6 +399,10 @@ func (c *ProfileClient) SyncBundleID(bundleID appstoreconnect.BundleID, appEntit ent := autocodesign.Entitlement{key: value} cap, err := ent.Capability() if err != nil { + if errors.Is(err, autocodesign.ErrUnknownEntitlementKey) { + log.Warnf("Skipping unknown entitlement key %q while syncing bundle ID capabilities. If this is a recognised Apple entitlement that should be registered on App Store Connect, please report it to bitrise-io/go-xcode so it can be added to ServiceTypeByKey.", key) + continue + } return err } if cap == nil { diff --git a/autocodesign/entitlement.go b/autocodesign/entitlement.go index cde18e67..78c0a54b 100644 --- a/autocodesign/entitlement.go +++ b/autocodesign/entitlement.go @@ -13,6 +13,14 @@ import ( // ICloudIdentifiersEntitlementKey ... const ICloudIdentifiersEntitlementKey = "com.apple.developer.icloud-container-identifiers" +// ErrUnknownEntitlementKey is returned by Entitlement.Capability and +// Entitlement.Equal when an entitlement key is not present in +// appstoreconnect.ServiceTypeByKey. Callers that iterate over a project's +// full entitlement set (e.g. SyncBundleID) may wrap this with errors.Is to +// skip unknown keys with a warning instead of aborting, so a single new +// Apple-introduced key does not break the entire code signing flow. +var ErrUnknownEntitlementKey = errors.New("unknown entitlement key") + // DataProtections ... var DataProtections = map[string]appstoreconnect.CapabilityOptionKey{ "NSFileProtectionComplete": appstoreconnect.CompleteProtection, @@ -44,7 +52,7 @@ func (e Entitlement) Capability() (*appstoreconnect.BundleIDCapability, error) { capType, ok := appstoreconnect.ServiceTypeByKey[entKey] if !ok { - return nil, errors.New("unknown entitlement key: " + entKey) + return nil, fmt.Errorf("%w: %s", ErrUnknownEntitlementKey, entKey) } if capType == appstoreconnect.Ignored { @@ -144,7 +152,7 @@ func (e Entitlement) Equal(cap appstoreconnect.BundleIDCapability, allEntitlemen capType, ok := appstoreconnect.ServiceTypeByKey[entKey] if !ok { - return false, errors.New("unknown entitlement key: " + entKey) + return false, fmt.Errorf("%w: %s", ErrUnknownEntitlementKey, entKey) } if cap.Attributes.CapabilityType != capType { diff --git a/autocodesign/entitlement_test.go b/autocodesign/entitlement_test.go index 61ac5e9b..b90a6cb0 100644 --- a/autocodesign/entitlement_test.go +++ b/autocodesign/entitlement_test.go @@ -1,6 +1,7 @@ package autocodesign import ( + "errors" "testing" "github.com/stretchr/testify/require" @@ -109,6 +110,18 @@ func TestCapability_HealthKitAccessIgnored(t *testing.T) { require.Nil(t, cap) } +func TestCapability_UnknownKeyReturnsSentinel(t *testing.T) { + ent := Entitlement(map[string]interface{}{ + "com.apple.developer.totally-made-up-entitlement": true, + }) + + cap, err := ent.Capability() + require.Error(t, err) + require.True(t, errors.Is(err, ErrUnknownEntitlementKey)) + require.Contains(t, err.Error(), "com.apple.developer.totally-made-up-entitlement") + require.Nil(t, cap) +} + func TestCapability_IgnoredKeys(t *testing.T) { keys := []string{ "com.apple.developer.kernel.extended-virtual-addressing",