diff --git a/Sources/VariantsCore/Extensions/CustomProperty+EnvironmentVar.swift b/Sources/VariantsCore/Extensions/CustomProperty+EnvironmentVar.swift deleted file mode 100644 index ca0763a4..00000000 --- a/Sources/VariantsCore/Extensions/CustomProperty+EnvironmentVar.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// Variants -// -// Copyright (c) Backbase B.V. - https://www.backbase.com -// Created by Giuseppe Deraco -// - -import Foundation - -typealias EnvironmentVariableHandler = (isEnvVar: Bool, string: String) - -extension CustomProperty { - func processForEnvironment() -> EnvironmentVariableHandler { - let regexPattern = #"^\{\{ envVars.(?.*) \}\}"# - - let regex = try? NSRegularExpression( - pattern: regexPattern - ) - - if let match = regex?.firstMatch(in: value, options: [], range: NSRange(location: 0, length: value.utf8.count)) { - if #available(OSX 10.13, *) { - if let envVarNameIndex = Range(match.range(withName: "name"), in: value) { - let envVarName = String(value[envVarNameIndex]) - switch destination { - case .project: - return (isEnvVar: true, string: envVarName) - case .fastlane: - return (isEnvVar: true, string: "ENV[\""+envVarName+"\"]") - } - } else { - return (isEnvVar: false, string: value) - } - } - } - return (isEnvVar: false, string: value) - } -} diff --git a/Sources/VariantsCore/Factory/Android/GradleScriptFactory.swift b/Sources/VariantsCore/Factory/Android/GradleScriptFactory.swift index 935bfdab..e14c0ebb 100644 --- a/Sources/VariantsCore/Factory/Android/GradleScriptFactory.swift +++ b/Sources/VariantsCore/Factory/Android/GradleScriptFactory.swift @@ -118,20 +118,16 @@ class GradleScriptFactory: GradleFactory { fileprivate extension Sequence where Iterator.Element == CustomProperty { func envVars() -> [CustomProperty] { return self - .filter({ $0.destination == .project && $0.processForEnvironment().isEnvVar }) + .filter({ $0.destination == .project && $0.isEnvironmentVariable }) .map { (property) -> CustomProperty in - let processed = property.processForEnvironment() - if processed.isEnvVar { - return CustomProperty(name: property.name, - value: "System.getenv('"+processed.string+"')", - destination: property.destination) - } - return property + return CustomProperty(name: property.name, + value: "System.getenv('"+property.environmentValue+"')", + destination: property.destination) } } func literal() -> [CustomProperty] { return self - .filter({ $0.destination == .project && !$0.processForEnvironment().isEnvVar }) + .filter({ $0.destination == .project && !$0.isEnvironmentVariable }) } } diff --git a/Sources/VariantsCore/Factory/FastlaneParametersFactory.swift b/Sources/VariantsCore/Factory/FastlaneParametersFactory.swift index 0ab51578..030fd49e 100644 --- a/Sources/VariantsCore/Factory/FastlaneParametersFactory.swift +++ b/Sources/VariantsCore/Factory/FastlaneParametersFactory.swift @@ -111,20 +111,16 @@ class FastlaneParametersFactory: ParametersFactory { fileprivate extension Sequence where Iterator.Element == CustomProperty { func envVars() -> [CustomProperty] { return self - .filter({ $0.destination == .fastlane && $0.processForEnvironment().isEnvVar }) + .filter({ $0.destination == .fastlane && $0.isEnvironmentVariable }) .map { (property) -> CustomProperty in - let processed = property.processForEnvironment() - if processed.isEnvVar { - return CustomProperty(name: property.name, - value: processed.string, - destination: property.destination) - } - return property + return CustomProperty(name: property.name, + value: property.environmentValue, + destination: property.destination) } } func literal() -> [CustomProperty] { return self - .filter({ $0.destination == .fastlane && !$0.processForEnvironment().isEnvVar }) + .filter({ $0.destination == .fastlane && !$0.isEnvironmentVariable }) } } diff --git a/Sources/VariantsCore/Factory/iOS/SecretsFactory.swift b/Sources/VariantsCore/Factory/iOS/SecretsFactory.swift index d65c6b34..e608c8fc 100644 --- a/Sources/VariantsCore/Factory/iOS/SecretsFactory.swift +++ b/Sources/VariantsCore/Factory/iOS/SecretsFactory.swift @@ -98,20 +98,16 @@ class SecretsFactory { fileprivate extension Sequence where Iterator.Element == CustomProperty { func envVars() -> [CustomProperty] { return self - .filter({ $0.destination == .project && $0.processForEnvironment().isEnvVar }) + .filter({ $0.destination == .project && $0.isEnvironmentVariable }) .map { (property) -> CustomProperty in - let processed = property.processForEnvironment() - if processed.isEnvVar { - return CustomProperty(name: property.name, - value: "os.environ.get('"+processed.string+"')", - destination: property.destination) - } - return property + return CustomProperty(name: property.name, + value: "os.environ.get('"+property.environmentValue+"')", + destination: property.destination) } } func literal() -> [CustomProperty] { return self - .filter({ $0.destination == .project && !$0.processForEnvironment().isEnvVar }) + .filter({ $0.destination == .project && !$0.isEnvironmentVariable }) } } diff --git a/Sources/VariantsCore/Schemas/Configuration.swift b/Sources/VariantsCore/Schemas/Configuration.swift index d2cfd461..bd9c4f03 100644 --- a/Sources/VariantsCore/Schemas/Configuration.swift +++ b/Sources/VariantsCore/Schemas/Configuration.swift @@ -13,9 +13,44 @@ public struct Configuration: Codable { } public struct CustomProperty: Codable { - public var name: String - public var value: String - public var destination: Destination + var name: String + var value: String + private var env: Bool? = false + private(set) var isEnvironmentVariable: Bool + var destination: Destination + + enum CodingKeys: String, CodingKey { + case name + case value + case env + case destination + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + var isEnvVar: Bool? + if container.contains(.env) { + isEnvVar = try container.decode(Bool?.self, forKey: .env) + } + + name = try container.decode(String.self, forKey: .name) + value = try container.decode(String.self, forKey: .value) + isEnvironmentVariable = isEnvVar ?? false + destination = try container.decode(Destination.self, forKey: .destination) + } + + public init( + name: String, + value: String, + env: Bool = false, + destination: Destination + ) { + self.name = name + self.value = value + self.isEnvironmentVariable = env + self.destination = destination + } public enum Destination: String, Codable { case project @@ -28,3 +63,15 @@ extension CustomProperty: Equatable { return lhs.name == rhs.name } } + +extension CustomProperty { + var environmentValue: String { + guard isEnvironmentVariable == true else { return value } + switch destination { + case .project: + return value + case .fastlane: + return "ENV[\""+value+"\"]" + } + } +} diff --git a/Sources/VariantsCore/Schemas/iOS/iOSVariant.swift b/Sources/VariantsCore/Schemas/iOS/iOSVariant.swift index 0fe4bebe..f268bfc0 100644 --- a/Sources/VariantsCore/Schemas/iOS/iOSVariant.swift +++ b/Sources/VariantsCore/Schemas/iOS/iOSVariant.swift @@ -37,7 +37,7 @@ public struct iOSVariant: Codable { } custom? - .filter { $0.destination == .project && !$0.processForEnvironment().isEnvVar } + .filter { $0.destination == .project && !$0.isEnvironmentVariable } .forEach({ config in customDictionary[config.name] = config.value }) diff --git a/Templates/android/variants-template.yml b/Templates/android/variants-template.yml index e76a6b64..6f5043db 100644 --- a/Templates/android/variants-template.yml +++ b/Templates/android/variants-template.yml @@ -29,7 +29,12 @@ android: # custom: - name: SAMPLE_PROPERTY - value: "{{ envVars.SAMPLE_ENVIRONMENT_VARIABLE }}" + value: SAMPLE_VALUE + destination: fastlane + + - name: SAMPLE_PROPERTY_FROM_ENVIRONMENT + value: SAMPLE_ENVIRONMENT_VARIABLE + env: true destination: fastlane # Sample variant "BETA" @@ -44,7 +49,8 @@ android: store_destination: AppCenter custom: - name: SAMPLE_PROPERTY - value: "{{ envVars.SAMPLE_ENVIRONMENT_VARIABLE }}" + value: SAMPLE_ENVIRONMENT_VARIABLE + env: true destination: fastlane # ---------------------------------------------------------------------- @@ -58,9 +64,9 @@ android: #signing: # key_alias: android - # key_password: "{{ envVars.APP_SIGN_KEY_PASSWORD }}" + # key_password: APP_SIGN_KEY_PASSWORD # store_file: /usr/local/android/production.keystore - # store_password: "{{ envVars.APP_SIGN_STORE_PASSWORD }}" + # store_password: APP_SIGN_STORE_PASSWORD # ---------------------------------------------------------------------- # custom: - Not required. @@ -73,8 +79,10 @@ android: #custom: # - name: mvnUser - # value: "{{ envVars.MAVEN_USERNAME }}" + # value: MAVEN_USERNAME + # env: true # destination: project # - name: mvnPass - # value: "{{ envVars.MAVEN_PASSWORD }}" + # value: MAVEN_PASSWORD + # env: true # destination: project diff --git a/Templates/ios/variants-template.yml b/Templates/ios/variants-template.yml index 438f507c..207ab225 100644 --- a/Templates/ios/variants-template.yml +++ b/Templates/ios/variants-template.yml @@ -45,9 +45,11 @@ ios: custom: - name: OTHER_SWIFT_FLAGS value: $(inherited) + env: false destination: project - name: SAMPLE_FASTLANE_PROPERTY value: This will be available to fastlane + env: false destination: fastlane # # Sample variant, "beta". @@ -76,9 +78,11 @@ ios: custom: - name: OTHER_SWIFT_FLAGS value: $(inherited) + env: false destination: project - name: SAMPLE_FASTLANE_PROPERTY value: This will be available to fastlane on Beta variant + env: false destination: fastlane signing: diff --git a/Tests/VariantsCoreTests/CustomProperty+EnvironmentVarTests.swift b/Tests/VariantsCoreTests/CustomProperty+EnvironmentVarTests.swift index 7cbf4ff7..fe89f96d 100644 --- a/Tests/VariantsCoreTests/CustomProperty+EnvironmentVarTests.swift +++ b/Tests/VariantsCoreTests/CustomProperty+EnvironmentVarTests.swift @@ -13,19 +13,20 @@ class CustomPropertyEnvironmentVarTests: XCTestCase { func testProcessForEnvironment_forProject_true() { let environmentVarProperty = CustomProperty( name: "AN_ENV_VAR", - value: "{{ envVars.A_SECRET }}", + value: "A_SECRET", + env: true, destination: .project ) XCTAssertTrue( - environmentVarProperty.processForEnvironment().isEnvVar, - "After processing `isEnvVar` should be true as it matches the pattern" + environmentVarProperty.isEnvironmentVariable, + "`isEnvironmentVariable` should be true as `env` is set to true" ) XCTAssertEqual( - environmentVarProperty.processForEnvironment().string, + environmentVarProperty.environmentValue, "A_SECRET", - "After processing `string` should be processed to extract env var name" + "`environmentValue` should be equal to `value` as `destination` is project" ) } @@ -37,33 +38,34 @@ class CustomPropertyEnvironmentVarTests: XCTestCase { ) XCTAssertFalse( - environmentVarProperty.processForEnvironment().isEnvVar, - "After processing `isEnvVar` should be false as it ddoesn't match the pattern" + environmentVarProperty.isEnvironmentVariable, + "`isEnvironmentVariable` should be false as `env` is not set and defaults to false" ) XCTAssertEqual( - environmentVarProperty.processForEnvironment().string, + environmentVarProperty.environmentValue, environmentVarProperty.value, - "After processing `string` should be exactly the same" + "`environmentValue` should be equal to `value` as `destination` is project and/or `env` isn't set and defaults to false" ) } func testProcessForEnvironment_forFastlane_true() { let environmentVarProperty = CustomProperty( name: "AN_ENV_VAR", - value: "{{ envVars.A_SECRET }}", + value: "A_SECRET", + env: true, destination: .fastlane ) XCTAssertTrue( - environmentVarProperty.processForEnvironment().isEnvVar, - "After processing `isEnvVar` should be true as it matches the pattern" + environmentVarProperty.isEnvironmentVariable, + "`isEnvironmentVariable` should be true as `env` is set to true" ) XCTAssertEqual( - environmentVarProperty.processForEnvironment().string, + environmentVarProperty.environmentValue, "ENV[\"A_SECRET\"]", - "After processing `string` should be processed to extract env var name" + "`environmentValue` should be contained within 'ENV[\"\"]' as `destination` is fastlane and `env` is set to true" ) } @@ -75,14 +77,14 @@ class CustomPropertyEnvironmentVarTests: XCTestCase { ) XCTAssertFalse( - environmentVarProperty.processForEnvironment().isEnvVar, - "After processing `isEnvVar` should be false as it ddoesn't match the pattern" + environmentVarProperty.isEnvironmentVariable, + "`isEnvironmentVariable` should be false as `env` is not set and defaults to false" ) XCTAssertEqual( - environmentVarProperty.processForEnvironment().string, + environmentVarProperty.environmentValue, environmentVarProperty.value, - "After processing `string` should be exactly the same" + "`environmentValue` should be equal to `value` as `destination`, as `env` isn't set and defaults to false" ) } @@ -95,7 +97,8 @@ class CustomPropertyEnvironmentVarTests: XCTestCase { ), CustomProperty( name: "AN_ENV_VAR", - value: "{{ envVars.A_SECRET }}", + value: "A_SECRET", + env: true, destination: .fastlane ) ] @@ -103,10 +106,9 @@ class CustomPropertyEnvironmentVarTests: XCTestCase { let fastlaneParameters = propertiesArray .filter { $0.destination == .fastlane } .map { (property) -> CustomProperty in - let processed = property.processForEnvironment() - if processed.isEnvVar { + if property.isEnvironmentVariable { return CustomProperty(name: property.name, - value: processed.string, + value: property.environmentValue, destination: property.destination) } return property diff --git a/Tests/VariantsCoreTests/FastlaneParametersFactoryTests.swift b/Tests/VariantsCoreTests/FastlaneParametersFactoryTests.swift index 2b0bf8fa..677c4fdc 100644 --- a/Tests/VariantsCoreTests/FastlaneParametersFactoryTests.swift +++ b/Tests/VariantsCoreTests/FastlaneParametersFactoryTests.swift @@ -17,7 +17,7 @@ let parameters = [ CustomProperty(name: "sample-3", value: "sample-3-value", destination: .project), CustomProperty(name: "sample-4", value: "sample-4-value", destination: .fastlane), CustomProperty(name: "sample-5", value: "sample-5-value", destination: .fastlane), - CustomProperty(name: "sample-env", value: "{{ envVars.API_TOKEN }}", destination: .fastlane) + CustomProperty(name: "sample-env", value: "API_TOKEN", env: true, destination: .fastlane) ] let correctOutput = @@ -157,20 +157,16 @@ class FastlaneParametersFactoryTests: XCTestCase { fileprivate extension Sequence where Iterator.Element == CustomProperty { func envVars() -> [CustomProperty] { return self - .filter({ $0.destination == .fastlane && $0.processForEnvironment().isEnvVar }) + .filter({ $0.destination == .fastlane && $0.isEnvironmentVariable }) .map { (property) -> CustomProperty in - let processed = property.processForEnvironment() - if processed.isEnvVar { - return CustomProperty(name: property.name, - value: processed.string, - destination: property.destination) - } - return property + return CustomProperty(name: property.name, + value: property.environmentValue, + destination: property.destination) } } func literal() -> [CustomProperty] { return self - .filter({ $0.destination == .fastlane && !$0.processForEnvironment().isEnvVar }) + .filter({ $0.destination == .fastlane && !$0.isEnvironmentVariable }) } } diff --git a/Tests/VariantsCoreTests/GradleScriptFactoryTests.swift b/Tests/VariantsCoreTests/GradleScriptFactoryTests.swift index 3d04ae39..b46f1568 100644 --- a/Tests/VariantsCoreTests/GradleScriptFactoryTests.swift +++ b/Tests/VariantsCoreTests/GradleScriptFactoryTests.swift @@ -66,7 +66,8 @@ class GradleScriptFactoryTests: XCTestCase { custom: [ CustomProperty( name: "API_TOKEN", - value: "{{ envVars.API_TOKEN }}", + value: "API_TOKEN", + env: true, destination: .project ) ] diff --git a/Tests/VariantsCoreTests/Resources/variants-template.yml b/Tests/VariantsCoreTests/Resources/variants-template.yml index dab3e6b8..4a861957 100644 --- a/Tests/VariantsCoreTests/Resources/variants-template.yml +++ b/Tests/VariantsCoreTests/Resources/variants-template.yml @@ -28,9 +28,11 @@ ios: custom: - name: OTHER_SWIFT_FLAGS value: $(inherited) + env: false destination: project - name: SAMPLE_FASTLANE_PROPERTY value: This will be available to fastlane + env: false destination: fastlane # # Sample variant, "beta". @@ -45,7 +47,9 @@ ios: custom: - name: OTHER_SWIFT_FLAGS value: $(inherited) + env: false destination: project - name: SAMPLE_FASTLANE_PROPERTY value: This will be available to fastlane on Beta variant + env: false destination: fastlane diff --git a/Variants.xcodeproj/project.pbxproj b/Variants.xcodeproj/project.pbxproj index 38361cec..08e3cf89 100644 --- a/Variants.xcodeproj/project.pbxproj +++ b/Variants.xcodeproj/project.pbxproj @@ -85,7 +85,6 @@ 8E8A48CA255307B20056F79F /* GradleScriptFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E8A48BE255305C50056F79F /* GradleScriptFactoryTests.swift */; }; 8E8A4909255420FD0056F79F /* PlatformDetectorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E8A4908255420FC0056F79F /* PlatformDetectorTests.swift */; }; 8E8A491025543F920056F79F /* SpecHelperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E8A490F25543F910056F79F /* SpecHelperTests.swift */; }; - 8EDC54D625554A3D00A9CDFF /* CustomProperty+EnvironmentVar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EDC54D525554A3D00A9CDFF /* CustomProperty+EnvironmentVar.swift */; }; 8EDC54E225554B8C00A9CDFF /* CustomProperty+EnvironmentVarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EDC54E125554B8C00A9CDFF /* CustomProperty+EnvironmentVarTests.swift */; }; 8EDC550C25592F5800A9CDFF /* iOSProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EDC550B25592F5800A9CDFF /* iOSProjectTests.swift */; }; 8EE24235256BA98C00F66F61 /* iOSSigning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8EE24234256BA98C00F66F61 /* iOSSigning.swift */; }; @@ -174,7 +173,6 @@ 8E8A48BE255305C50056F79F /* GradleScriptFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradleScriptFactoryTests.swift; sourceTree = ""; }; 8E8A4908255420FC0056F79F /* PlatformDetectorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformDetectorTests.swift; sourceTree = ""; }; 8E8A490F25543F910056F79F /* SpecHelperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecHelperTests.swift; sourceTree = ""; }; - 8EDC54D525554A3D00A9CDFF /* CustomProperty+EnvironmentVar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomProperty+EnvironmentVar.swift"; sourceTree = ""; }; 8EDC54E125554B8C00A9CDFF /* CustomProperty+EnvironmentVarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomProperty+EnvironmentVarTests.swift"; sourceTree = ""; }; 8EDC550B25592F5800A9CDFF /* iOSProjectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSProjectTests.swift; sourceTree = ""; }; 8EE24234256BA98C00F66F61 /* iOSSigning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSSigning.swift; sourceTree = ""; }; @@ -364,7 +362,6 @@ OBJ_26 /* String+Error.swift */, OBJ_27 /* String+Write.swift */, 8E8A4865255172CF0056F79F /* Path+SafeJoin.swift */, - 8EDC54D525554A3D00A9CDFF /* CustomProperty+EnvironmentVar.swift */, ); path = Extensions; sourceTree = ""; @@ -726,7 +723,6 @@ 8E1B9D9D254AC26F00DD0204 /* Platform.swift in Sources */, 8E1B9EFC254AC2A900DD0204 /* iOSConfiguration.swift in Sources */, 8E1B9E3B254AC28F00DD0204 /* XCConfigFactory.swift in Sources */, - 8EDC54D625554A3D00A9CDFF /* CustomProperty+EnvironmentVar.swift in Sources */, 8E1B9E36254AC28F00DD0204 /* GradleScriptFactory.swift in Sources */, 8E1B9DF3254AC27900DD0204 /* TemplateDirectory.swift in Sources */, 8E1B9DC7254AC27500DD0204 /* Project.swift in Sources */, diff --git a/docs/CUSTOM_PROPERTY.md b/docs/CUSTOM_PROPERTY.md index 515f5ec4..40c65fc2 100644 --- a/docs/CUSTOM_PROPERTY.md +++ b/docs/CUSTOM_PROPERTY.md @@ -3,11 +3,14 @@ Your `variants.yml` spec is composed of different objects per platform, which are likely mandatory, but it also introduces an optional property called `"custom"`, which is either a platform property and/or a variant property. -A custom property consists of 3 values: +A custom property consists of 4 values: -- `name`: Name of the property -- `value`: Value of the property -- `destination`: Destination of the property +| Value | Explanation | Default | Required | +| ------- | ------------- | ----------- | --------- | +| `name` | Name of the property. | N/A | Yes | +| `value` | Value of the property. If `env` is set to `true` it refers to the name of an environment variable | N/A | Yes | +| `env` | Boolean to specify if value is the name of an environment variable. | false | No | +| `destination` | Destination of the property. It is either `fastlane` or `project`. | N/A | Yes | ### Destination @@ -100,39 +103,4 @@ Above, both `default` and `BETA` contain a `"BASE_URL"` property, which can be u ## Working with Environment Variables -We often find ourselves in need to use an API token - or any other secret - in our codebase or CD setup that shouldn't, by any means, be hardcoded nor committed in the repository. It's common in these situations to use _environment variables_. - -Custom properties support environment variable values, that will provide the destination (`fastlane` or `project`) access to those values in the appropriate manner. The syntax is simple: - -```yaml -custom: - - name: NAME_OF_PROPERTY - value: "{{ envVars.NAME_OF_ENV_VAR }}" - destination: project - - name: DEPLOYMENT_API_TOKEN - value: "{{ envVars.APPCENTER_API_TOKEN }}" - destination: fastlane -``` - -#### Destination `project` - -- Android -Such a property will be written to `variants.gradle` -```gradle -// ==== Custom values ==== -rootProject.ext.NAME_OF_PROPERTY = System.getenv('NAME_OF_ENV_VAR') -``` -- iOS -Currently, destination `project` with an environment variable value is not supported. It will not write anything to `variants.xcconfig`. -iOS implementation depends on [Issue #87](https://github.com/Backbase/variants/issues/87) - -#### Destination `fastlane` - -These properties will continue to be written to `fastlane/parameters/variants_params.rb` and used the same way as a normal property. -The only difference is where the value comes from: - -```ruby -VARIANTS_PARAMS = { - DEPLOYMENT_API_TOKEN: ENV["APPCENTER_API_TOKEN"], -}.freeze -``` +Custom Properties support the use of environment variables, whenever there is the need to expose information to an Android/iOS project or to Fastlane without hardcoding and/or committing its value. This is often used for secrets/tokens. See [Working with Environment Variables](ENVIRONMENT_VARIABLES.md) for examples. diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md new file mode 100644 index 00000000..04013837 --- /dev/null +++ b/docs/ENVIRONMENT_VARIABLES.md @@ -0,0 +1,113 @@ +## Working with Environment Variables + +> This documentation relates to [Custom Property](CUSTOM_PROPERTY.md). + +We often find ourselves in need to use an API token - or any other secret - in our codebase or CD setup that shouldn't, by any means, be hardcoded nor committed in the repository. It's common in these situations to use _environment variables_. + +Custom properties support environment variable values, that will provide the destination (`fastlane` or `project`) access to those values in the appropriate manner. This is done through the `env` key. + +### Examples + +Let's assume you have the following line in the file `~/.bash_profile`, exporting the environment variable `FOO`: + +```bash +export FOO="Once upon a time there was a king..." +``` + +#### Destination `fastlane` + +Now, let's create 3 custom properties in `variants.yml` and see how they will be used, depending on the `env` key. + +```yaml +custom: + - name: A_PROPERTY + value: FOO + destination: fastlane + + - name: B_PROPERTY + value: FOO + env: false + destination: fastlane + + - name: C_PROPERTY + value: FOO + env: true + destination: fastlane +``` + +In the example above, all custom properties are set to destination `fastlane`, which means all 3 will be exposed to `fastlane/parameters/variants_params.rb` as follows: + +```ruby +VARIANTS_PARAMS = { + A_PROPERTY: "FOO", + B_PROPERTY: "FOO", + C_PROPERTY: ENV["FOO"] +}.freeze +``` + +#### Destination `project` + +- When destination is set to `project` and platform is Android, such properties will be written to `variants.gradle`. + +```gradle +// ==== Custom values ==== +rootProject.ext.A_PROPERTY = "FOO" +rootProject.ext.B_PROPERTY = "FOO" +rootProject.ext.C_PROPERTY = System.getenv('FOO') +``` + +- When platform is iOS, these properties behave in a slightly different way. + +Properties whose destination is `project`, for iOS, that are **not** reading from environment variables, will be available in `variants.xcconfig`. + +``` +A_PROPERTY = FOO +B_PROPERTY = FOO +``` + +These are used in your Swift code as: +```swift +Variants.configuration["A_PROPERTY"] +``` + +However, properties whose values are read from environment variables are exposed to the codebase directly in `Variants/Variants.swift`, as static variables within a `Secrets` type. +In Swift, properties can't read directly from environment variables, therefore Variants encrypts these values with a xor cipher using a salt that's generated randomly each time. + +```swift +// This entire file is automatically generated. + +public struct Variants { + static let configuration: [String: Any] = { + guard let infoDictionary = Bundle.main.infoDictionary else { + fatalError("Info.plist file not found") + } + return infoDictionary + }() + + // Encrypted secrets coming from variants.yml as environment variables + public struct Secrets { + private static let salt: [UInt8] = [ + // Randomly generated salt + ... + ] + + static var C_PROPERTY: String { + let encoded: [UInt8] = [ + // Encrypted value of environment variable 'FOO' + ... + + return decode(encoded, cipher: salt) + ] + } + + ... + } +``` + +This guarantees a minimal level of security by not exposing the value of environment variable 'FOO' directly into the source code. +The property can now be used anywhere in the codebase as in the example below: + +```swift +> print(Variants.Secrets.C_PROPERTY) +"Once upon a time there was a king..." +``` diff --git a/docs/USAGE.md b/docs/USAGE.md index cea70b03..8484f0a3 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -41,7 +41,7 @@ $ variants init --platform ios It will generate a variants.yml file in the base folder of your project

- +

> NOTE: Edit the file variants.yml accordingly. @@ -71,6 +71,7 @@ ios: default: version_name: 0.0.1 version_number: 1 + store_destination: AppStore custom: - name: apiBaseUrl value: https://sample.com/ @@ -80,6 +81,7 @@ ios: app_icon: AppIcon.beta version_name: 0.0.1 version_number: 13 + store_destination: TestFlight custom: - name: apiBaseUrl value: https://sample-beta.com/ @@ -93,7 +95,7 @@ ios: Configuration through custom properties can bring a lot of value to your variants, such as defining different API base URLs, or credentials using environment variables. This allows us to also define its destination. Certain properties should not be available to the project but to fastlane and vice-versa. -See our [Custom Property documentation](docs/CUSTOM_PROPERTY.md) for a better understanding and examples. +See our [Custom Property documentation](CUSTOM_PROPERTY.md) for a better understanding and examples. ### Setup multiple build variants with full fastlane integration. @@ -131,7 +133,7 @@ let baseUrl = Variants.configuration["apiBaseURL"] Setup will also configure your Xcode project to use this new configuration and map configs (such as `name`, `bundle_id`, `app_icon`, `version_name` and `version_number`).

- +

#### Using a configuration file other than the default one @@ -175,3 +177,8 @@ $ variants switch --variant beta # Specify platform (in case there are projects for different platforms in the working directory, this will be mandatory) $ variants switch --variant beta --platform ios ``` + +### Signing iOS apps + +Code signing for iOS apps can also be handled through `variants.yml` as long as Fastlane Match is used. +For more information see [Working with Fastlane Match](ios/WORKING_WITH_FASTLANE_MATCH.md).