github.com/konsorten/ktn-build-info@v1.0.11/ver/consul_build_id.go (about)

     1  package ver
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"strconv"
     7  
     8  	"github.com/hashicorp/consul/api"
     9  )
    10  
    11  func RetrieveBuildFromConsul(consulUrl string, kvProjectRoot string, vi *VersionInformation) error {
    12  	// check version info
    13  	if kvProjectRoot == "" {
    14  		return fmt.Errorf("Consul KV project root path may not be empty")
    15  	}
    16  
    17  	if vi.Revision == "" {
    18  		return fmt.Errorf("No revision information available")
    19  	}
    20  
    21  	// parse the url
    22  	cu, err := url.Parse(consulUrl)
    23  
    24  	if err != nil {
    25  		return fmt.Errorf("Failed to parse consul URL: %v", err)
    26  	}
    27  
    28  	// assign config
    29  	cfg := api.DefaultConfig()
    30  
    31  	cfg.Address = cu.Host
    32  	cfg.Scheme = cu.Scheme
    33  	cfg.Datacenter = cu.Path[1:] // trim first slash
    34  
    35  	if cu.User != nil {
    36  		if t, ok := cu.User.Password(); ok {
    37  			cfg.Token = t
    38  		}
    39  	}
    40  
    41  	// connect
    42  	client, err := api.NewClient(cfg)
    43  
    44  	if err != nil {
    45  		return fmt.Errorf("Failed to connect to consul: %v", err)
    46  	}
    47  
    48  	kv := client.KV()
    49  
    50  	// create session
    51  	session, _, err := client.Session().Create(&api.SessionEntry{TTL: "60s", Behavior: "delete"}, nil)
    52  
    53  	if err != nil {
    54  		return fmt.Errorf("Failed to create session: %v", err)
    55  	}
    56  
    57  	defer client.Session().Destroy(session, nil)
    58  
    59  	// acquire revision
    60  	revPath := fmt.Sprintf("%v/revs/%v", kvProjectRoot, vi.Revision)
    61  	lockPath := fmt.Sprintf("%v/~lock", revPath)
    62  
    63  	locked, _, err := kv.Acquire(&api.KVPair{Key: lockPath, Session: session}, nil)
    64  
    65  	if err != nil {
    66  		return fmt.Errorf("Failed to acquire lock: %v", err)
    67  	}
    68  
    69  	if !locked {
    70  		return fmt.Errorf("Failed to acquire lock on KV path: %v", lockPath)
    71  	}
    72  
    73  	defer kv.Release(&api.KVPair{Key: lockPath, Session: session}, nil)
    74  
    75  	// retrieve any existing
    76  	build, _, err := kv.Get(fmt.Sprintf("%v/build", revPath), nil)
    77  
    78  	if err != nil {
    79  		return fmt.Errorf("Failed to retrieve existing build id: %v", err)
    80  	}
    81  
    82  	if build != nil {
    83  		b, err := strconv.Atoi(string(build.Value))
    84  
    85  		if err != nil {
    86  			return fmt.Errorf("Failed to parse existing build id: %v", err)
    87  		}
    88  
    89  		vi.Build = b
    90  
    91  		return nil
    92  	}
    93  
    94  	// get new build id
    95  	for true {
    96  		nextIdPath := fmt.Sprintf("%v/nextId", kvProjectRoot)
    97  
    98  		nextId, _, err := kv.Get(nextIdPath, nil)
    99  
   100  		if err != nil {
   101  			return fmt.Errorf("Failed to get next build id: %v", err)
   102  		}
   103  
   104  		if nextId != nil {
   105  			nextId.Session = session
   106  
   107  			// parse the id
   108  			vi.Build, err = strconv.Atoi(string(nextId.Value))
   109  
   110  			if err != nil {
   111  				return fmt.Errorf("Failed to parse next build id: %v", err)
   112  
   113  			}
   114  
   115  			// increment the id
   116  			nextId.Value = []byte(strconv.Itoa(vi.Build + 1))
   117  		} else {
   118  			// use 1 as first ID, and register 2 as the next one
   119  			vi.Build = 1
   120  
   121  			nextId = &api.KVPair{Key: nextIdPath, Value: []byte("2"), Session: session}
   122  		}
   123  
   124  		// update the id
   125  		ok, _, err := kv.CAS(nextId, nil)
   126  
   127  		if ok {
   128  			break
   129  		}
   130  	}
   131  
   132  	// write the revision
   133  	_, err = kv.Put(&api.KVPair{
   134  		Key:     fmt.Sprintf("%v/build", revPath),
   135  		Value:   []byte(strconv.Itoa(vi.Build)),
   136  		Session: session,
   137  	}, nil)
   138  
   139  	if err != nil {
   140  		return fmt.Errorf("Failed to write build revision: %v", err)
   141  	}
   142  
   143  	// done
   144  	return nil
   145  }