Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

DevOps sur Android : from one git push to a Pla...

DevOps sur Android : from one git push to a Play Store release

Slides in English.

Talk given à Devoxx Belgium and Droidcon Vienna

Jeremie Martinez

November 10, 2016
Tweet

More Decks by Jeremie Martinez

Other Decks in Technology

Transcript

  1. DevOps on Android From one git push to a Play

    Store release @JeremMartinez www.jeremie-martinez.com
  2. DevOps n.pr [dɛvops] Promotes a release process which bring together

    through communication and collaboration both development and operational teams. Play Store
  3. Merge request mandatory At least one reviewer Merge always by

    the reviewer Branch must always be rebased Push
  4. JUnit4 public final class LordOfTheRingsTest {
 
 @Before
 public void

    setup() { … }
 
 @Test
 public void shouldBringThePrecious() { … } @After
 public void tearDown() { … } } Unit tests
  5. Robolectric public final class LordOfTheRingsTest {
 
 @Before
 public void

    setup() { … }
 
 @Test
 public void shouldBringThePrecious() { … } @After
 public void tearDown() { … } } Unit tests
  6. @RunWith(RobolectricTestRunner.class)
 @Config(manifest = Config.NONE) Unit tests Robolectric public final class

    LordOfTheRingsTest {
 
 @Before
 public void setup() { … }
 
 @Test
 public void shouldBringThePrecious() { … } @After
 public void tearDown() { … } }
  7. Quality public class AttrPrefixDetector extends ResourceXmlDetector {
 
 public static

    final Issue ISSUE = Issue.create("AttrNotPrefixed", //
 "You must prefix your custom attr by `ct`", //
 "We prefix all our attrs to avoid clashes.", //
 Category.TYPOGRAPHY, //
 5, // Priority
 Severity.WARNING, //
 new Implementation(AttrPrefixDetector.class, // Scope.RESOURCE_FILE_SCOPE) // );
  8. Quality // Only XML files
 @Override
 public boolean appliesTo(@NonNull Context

    context,
 @NonNull File file) {
 return LintUtils.isXmlFile(file);
 }
  9. Quality @Override
 public void visitElement(XmlContext context, Element element) {
 final

    Attr attributeNode = element.getAttributeNode(ATTR_NAME);
 if (attributeNode != null) {
 final String val = attributeNode.getValue();
 if (!val.startsWith("android:") && !val.startsWith("ct")) {
 context.report(ISSUE,
 attributeNode,
 context.getLocation(attributeNode),
 "You must prefix your custom attr by `ct`");
 }
 }
 }
  10. Quality public final class CaptainRegistry extends IssueRegistry {
 @Override
 public

    List<Issue> getIssues() {
 return Collections.singletonList(AttrPrefixDetector.ISSUE);
 }
 }
  11. Automate releases in order to deliver quickly, easily and reliably

    Continuous integration Continuous delivery
  12. private static final String[] EXPECTED_PERMISSIONS = { … } @Test


    public void shouldMatchPermissions() throws Exception {
 final AndroidManifest androidManifest = new AndroidManifest( //
 Fs.fileFromPath("build/intermediates/manifests/full/debug/AndroidManifest.xml"), //
 null, //
 null //
 );
 final Set<String> requestedPermissions = new HashSet<>(androidManifest.getUsedPermissions());
 
 assertThat(requestedPermissions).containsOnly(EXPECTED_PERMISSIONS);
 } Publish
  13. @Test
 public void upgradeShouldBeTheSameAsCreate() throws Exception {
 DbOpenHelper helper =

    new DbOpenHelper(RuntimeEnvironment.application);
 
 helper.onCreate(newDatabase());
 helper.onUpgrade(originDatabase(), 1, CURRENT_VERSION);
 
 Set<String> newSchema = extractSchema(newFile.getAbsolutePath());
 Set<String> upgradedSchema = extractSchema(upgradedFile.getAbsolutePath());
 
 assertThat(upgradedSchema).isEqualTo(newSchema);
 } Publish
  14. connection = DriverManager.getConnection(JDBC_SQLITE + url);
 
 tables = connection.getMetaData().getTables(null, null,

    null, null);
 while (tables.next()) {
 
 final String tableType = tables.getString("TABLE_TYPE");
 final String tableName = tables.getString("TABLE_NAME");
 schema.add(tableType + " " + tableName);
 Publish
  15. columns = connection.getMetaData().getColumns(null, null, tableName, null);
 while (columns.next()) {
 


    final String columnName = columns.getString("COLUMN_NAME");
 final String columnType = columns.getString("TYPE_NAME");
 final String columnNullable = columns.getString("IS_NULLABLE");
 final String columnDefault = columns.getString("COLUMN_DF");
 schema.add("TABLE " + tableName +
 " COLUMN " + columnName + " " + columnType +
 " NULLABLE=" + columnNullable +
 " DEFAULT=" + columnDefault);
 } Publish
  16. http = GoogleNetHttpTransport.newTrustedTransport();
 json = JacksonFactory.getDefaultInstance();
 
 GoogleCredential credential =

    new GoogleCredential.Builder().
 setTransport(http).
 setJsonFactory(json).
 setServiceAccountPrivateKeyId(KEY_ID).
 setServiceAccountId(SERVICE_ACCOUNT_EMAIL).
 setServiceAccountScopes(AndroidPublisherScopes.ANDROIDPUBLISHER).
 setServiceAccountPrivateKeyFromPemFile(secretFile).
 build();
 
 publisher = new AndroidPublisher.Builder(http, json, credential).
 setApplicationName(PACKAGE).
 build(); Publish
  17. Listings listings = edits.listings(); Listing listing = new Listing().
 setFullDescription(description).


    setShortDescription(shortDescription).
 setTitle(title);
 
 listings.update(PACKAGE, id, "en_US", listing).execute(); Publish
  18. Images images = edits.images();
 
 FileContent content = new FileContent(PNG_MIME_TYPE,

    file);
 
 images.upload(PACKAGE, id, "en_US", "phone5", content).execute(); Publish "tablet7" "tablet9"
 "wear"
  19. 
 Apks apks = edits.apks(); 
 FileContent apkContent = new

    FileContent(APK_MIME_TYPE, apkFile);
 Apk apk = apks.upload(PACKAGE, id, apkContent).execute(); 
 int version = apk.getVersionCode();
 Publish
  20. 
 // Assign APK to Track
 Tracks tracks = edits.tracks();

    
 Track track = new Track().setVersionCodes(version); 
 tracks.update(PACKAGE, id, "production", track).execute(); Publish "rollout" "beta" "alpha"
  21. 
 // Update APK listing
 Apklistings apklistings = edits.apklistings(); 


    ApkListing whatsnew = new ApkListing().setRecentChanges(changes); 
 apklistings.update(PACKAGE, id, version, "en_US", whatsnew).execute(); Publish