Vous avez trouvé un bug dans un projet Go, ou vous avez ajouté une fonctionnalité et vous voulez en faire profiter le reste du monde ? La démarche pour cela est généralement trés simple, vous créez un fork du projet original, vous faites vos modifications et vous les soumettez en créant une pull request.

Avec Go ça se complique un peu en raison des chemins utilisés pour importer les différents modules utilisés dans le code.
Si on fork le repo sous:

github/moi/repo

Les chemins d’import vont toujours être sous la forme:

import github/orginal/repo/package

Et la deux cas de figure:

  • soit on a le repo original dans son $GOPATH et on va compiler en l’utilisant, autrement dit le binaire produit ne va pas tenir compte de nos modifications.
  • soit on ne l’a pas et dans ce cas on va avoir une erreur à la compilation.

On pourrait changer tous les chemins d’import de notre fork, mais dans ce cas on ne pourrait plus faire de pull request, enfin il se serait pas impossible d’en faire mais si le mainteneur de l’original est distrait et l’accepte, il va tout casser car ces imports pointeront vers notre fork.

Du coup comment allons nous faire ???

  • on va forker
  • on fait les modification sur l’original
  • on push nos modifications sur notre fork
  • on crée la pull request
  • et voila !

Voyons ça ne détail:

Un bug !!! 😱

Pour que ce soit plus parlant, je vais prendre un exemple concret: dernierement j’ai été amené à développer un service gérant la téléphonie d’une TPE. Pour faire court, il fallait dispatcher automatiquement les differents appels entrants en fonction du numéro appelé et des plages horaires. Pour cela j’ai utilisé les services VOIP de Twilio en codant un service HTTP servant du TwiML.
Concrétement quand un numéro est appelé, Twilio fait une requete HTTP vers une URL définie (notre service HTTP) qui va lui retourner des directives sur ce qui doit être fait ensuite. Je ne vais pas m’étendre davantage, si ce que l’on peut faire avec Twilio vous interesse, dites le moi en commentaire, je ferrais un billet dédié avec des exemples en Go.

Toujours est il que j’ai été amené à utiliser le package github.com/BTBurke/twiml permettant de produire du TwiML (du XML) facilement depuis Go et j’ai été confronté à un bug

Si on regarde la méthode Validate() de la structure Dial on voit que la propriété Number est requise:

// Validate returns an error if the TwiML is constructed improperly
func (d *Dial) Validate() error {
	var errs []error
	for _, s := range d.Children {
		switch t := s.Type(); t {
		default:
			return fmt.Errorf("Not a valid verb under Dial: '%T'", s)
		case "Client", "Conference", "Number", "Queue", "Sip":
			if childErr := s.Validate(); childErr != nil {
				errs = append(errs, childErr)
			}
		}
	}


	ok := Validate(
		OneOfOpt(d.Method, "GET", "POST"),
		Required(d.Number),
	)
	if !ok {
		errs = append(errs, fmt.Errorf("Dial did not pass validation"))
	}


	if len(errs) > 0 {
		return ValidationError{errs}
	}
	return nil
}

Or dans notre cas on ne va pas transferer l’appel vers un une ligne associée à un numéro de téléphone mais vers une ligne SIP, autrement dit dial.Numberne va pas être renseigné et à la place on va ajouter un dial.Children qui va contenir les informations nécessaires pour joindre la ligne SIP.

La correction

Pour corriger ce bug on va modifier le code de sorte qu’il vérifie si le dial.Number est présent, uniquement si il n’y a pas un dial.Children qui défini un appel SIP:



// Validate returns an error if the TwiML is constructed improperly
func (d *Dial) Validate() error {
	var errs []error
	var hasSIPChild bool
	for _, s := range d.Children {
		switch t := s.Type(); t {
		default:
			return fmt.Errorf("Not a valid verb under Dial: '%T'", s)
		case "Client", "Conference", "Number", "Queue", "Sip":
			if t == "Sip" {
				hasSIPChild = true
			}
			if childErr := s.Validate(); childErr != nil {
				errs = append(errs, childErr)
			}
		}
	}

	ok := Validate(
		OneOfOpt(d.Method, "GET", "POST"),
	)
	if ok && !hasSIPChild {
		ok = Validate(Required(d.Number))
	}
	if !ok {
		errs = append(errs, fmt.Errorf("Dial did not pass validation"))
	}

	if len(errs) > 0 {
		return ValidationError{errs}
	}
	return nil
}

Use the fork !

Nos modifications sont faites, on va à présent forker le repo original. Avec Github il vous suffit de cliquer sur le petit bouton fork:

fork guthub

Ajout d’une nouvelle destination remote

L’astuce va consister à pousser les modifications faites sur le repo original vers notre fork. Pour cela on ajoute un repo remote que l’on va nommer fork au projet original:

git remote add fork git@github.com:toorop/twiml.git

Attention: dans notre exemple on veut juste corriger un bug, pas participer activement au projet. Cette précision est importante, car dans le second cas, autrement dit si compte compte suivre l’activité du repo distant on va vite avoir des problémes de conflits. Il faudra dans ce cas être un peu plus subtil. Pour rester simple dans ce billet, on ne vas pas tenir compte de ce cas de figure.

Maintenant on peut pousser nos modifications sur notre fork:

git push fork

et faire notre demande de pull request.

A ce sujet avant de faire la demande de PR:

  • assurez vous que votre code fonstionne (oui je sais mais bon). Si le projet d’origine comprend des tests exécutez les, si votre modification apporte des fonctionnalités coder les tests qui vont avec.
  • consulter la doc du projet d’origine pour voir si il y a des mentions particulières concernant la participation au développement.

Voila vous n’avez plus d’excuses pour participer activement aux projets open-source Go 😎

PS: si ce tuto vous a plu pensez à le partager ⤵️