<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Golang on despatches</title><link>https://icle.es/tags/golang/</link><description>Recent content in Golang on despatches</description><generator>Hugo</generator><language>en</language><lastBuildDate>Wed, 18 Mar 2026 20:33:52 +0000</lastBuildDate><atom:link href="https://icle.es/tags/golang/index.xml" rel="self" type="application/rss+xml"/><item><title>Projector: Keep YouTube Descriptions synced</title><link>https://icle.es/2025/07/07/projector-keep-youtube-descriptions-synced/</link><pubDate>Mon, 07 Jul 2025 20:08:21 +0100</pubDate><guid>https://icle.es/2025/07/07/projector-keep-youtube-descriptions-synced/</guid><description>&lt;p>In my &lt;a href="https://icle.es/projector-Hugo.md">previous post&lt;/a>, I used &lt;a href="https://gohugo.io/">hugo&lt;/a> to
generate correctly linked, always up to date descriptions for my YouTube Videos.&lt;/p>
&lt;p>But if I&amp;rsquo;m generating the descriptions automatically&amp;hellip; I&amp;rsquo;m hardly going to be
excited about copying and pasting them into YouTube - right? right!&lt;/p>
&lt;p>Automating this process brings up a few design choices.&lt;/p>
&lt;h2 id="planning">Planning&lt;/h2>
&lt;h3 id="which-language">Which language&lt;/h3>
&lt;p>There were a few contenders, and here&amp;rsquo;s how I thought them through:&lt;/p>
&lt;h4 id="zig">Zig&lt;/h4>
&lt;p>I’m currently learning &lt;a href="https://ziglang.org/">Zig&lt;/a>, and I love using it for my
game development. But it doesn’t yet have mature libraries for working with the
YouTube Data API - and I don’t feel like writing one. So, sadly, Zig’s out for
this one.&lt;/p></description><content:encoded><![CDATA[<p>In my <a href="https://icle.es/projector-Hugo.md">previous post</a>, I used <a href="https://gohugo.io/">hugo</a> to
generate correctly linked, always up to date descriptions for my YouTube Videos.</p>
<p>But if I&rsquo;m generating the descriptions automatically&hellip; I&rsquo;m hardly going to be
excited about copying and pasting them into YouTube - right? right!</p>
<p>Automating this process brings up a few design choices.</p>
<h2 id="planning">Planning</h2>
<h3 id="which-language">Which language</h3>
<p>There were a few contenders, and here&rsquo;s how I thought them through:</p>
<h4 id="zig">Zig</h4>
<p>I’m currently learning <a href="https://ziglang.org/">Zig</a>, and I love using it for my
game development. But it doesn’t yet have mature libraries for working with the
YouTube Data API - and I don’t feel like writing one. So, sadly, Zig’s out for
this one.</p>
<h4 id="python">Python</h4>
<p>I used Python for <a href="https://icle.es/despatches.md">despatches</a> and it was the right fit there -
good libraries for BlueSky and Reddit.</p>
<p>However, I did not enjoy the experience:</p>
<ul>
<li>
<p><code>bazel</code> was a constant struggle</p>
</li>
<li>
<p><code>poetry</code> is nice… but still a bit of a nightmare. It just makes the pain more
structured</p>
</li>
<li>
<p>Worst of all: blusky failed after reddit succeeded caused a <em>partial success</em>,
which broke the Git commit and silently caused a post to be repeated
(embarrassing!)</p>
<p>That kind of problem <em>can</em> happen in Go (nil pointer), though it wouldn’t in
Zig. But at least with Go, most handleable errors <em>stay</em> errors — they don’t
crash the whole tool.</p>
</li>
</ul>
<h4 id="java">java</h4>
<p>Sure, I could do this in Java - but I really don’t want to mess with the JVM.
And more importantly, I’m doing this for fun. Java doesn’t feel like that
anymore.</p>
<h4 id="golang">golang</h4>
<p>Not quite my favourite any more, but still a close second. It&rsquo;s <em>fast</em>, has
YouTube libraries and it somehow seems fitting that Hugo is also a go baby.</p>
<p>Even though I’m not wiring the two directly, the ecosystem fit is nice.</p>
<h2 id="overall-plan">Overall Plan</h2>
<ul>
<li>
<p>Let <code>Hugo</code> render the YouTube description as plain text</p>
</li>
<li>
<p>Traverse the <code>youtube/*.md</code> files in the source directory</p>
<ul>
<li>Skip videos that are too old to update (maybe older than 30 days?)</li>
<li>Hash the rendered output (title, description, tags, etc.)</li>
<li>Compare that hash with the one stored in the frontmatter</li>
<li>If it doesn’t match,
<ul>
<li>Update the metadata on YouTube</li>
<li>Update the hash</li>
</ul>
</li>
<li>commit and push any updates (should be only hash changes)</li>
</ul>
</li>
</ul>
<h2 id="validation">Validation</h2>
<p>One thing worth being careful about is whether the metadata is valid. We do not
want the sync to fail during its scheduled run - when it won&rsquo;t have many choices
on how to resolve it.</p>
<p>In a bid to mitigate this, we&rsquo;ll add a command to validate the source and
rendered files.</p>
<p>The validation would expect the rendered files to be generated as well, which
seems reasonable since Hugo is probably running as <code>hugo serve</code> while the
content files are being updated.</p>
```go
func validate(sourcePath string, renderedPath string) error {
	targetSourceDir, err := getTargetDir(sourcePath)
	if err != nil {
		return err
	}

	targetRenderedDir, err := getTargetDir(renderedPath)
	if err != nil {
		return nil
	}

	videos, err := findRecentVideos(targetSourceDir)
	for _, video := range videos {
		_, err := video.getDescription(targetRenderedDir)
		if err != nil {
			slog.Warn("unable to find rendered file", "file", video.renderedPath)
		}
	}
	return nil
}
```
<p>The validate function will retrieve the relevant files and check that there is a
corresponding rendered description.</p>
<p>If it errors in that process, we know that it would error out in the sync.</p>
<p>We can&rsquo;t catch errors around the API though at this stage, and that&rsquo;s
unavoidable.</p>
<h2 id="sync">Sync</h2>
<h3 id="hashing-the-description">Hashing the Description</h3>
<p>This part was surprisingly easy:</p>
```go
bdesc, err := video.getDescription(targetRenderedDir)
if err != nil {
    slog.Warn("unable to find rendered file", "file", video.renderedPath)
}

// We want to hash the contents of description
// Check with the hash in the metadata to see if it matches
hash := md5.Sum(bdesc)
strHash := hex.EncodeToString(hash[:])
```
<p>The challenge was trying to write the updated yaml frontmatter back. I was using
the <code>adrg/frontmatter</code> library to read the frontmatter, but it does not support
writing it back.</p>
<h3 id="detour-write-a-small-frontmatter-library">Detour: Write a small frontmatter Library</h3>
<p>I took a little detour to build
<a href="https://icle.es/golang/inscribe.md">inscribe, a little frontmatter library that supports reading and writing back in yaml</a>.</p>
<h2 id="auth">Auth</h2>
<p>We need the YouTube Client to have an OAuth Token, which we can retrieve by:</p>
<ul>
<li><a href="https://console.cloud.google.com/auth/clients">Create a new OAuth Client</a> -
Type of desktop is probably the easiest</li>
<li>add your user account to
<a href="https://console.cloud.google.com/auth/audience">test users</a>:</li>
<li>go to
<a href="https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&amp;redirect_uri=urn:ietf:wg:oauth:2.0:oob&amp;response_type=code&amp;scope=https://www.googleapis.com/auth/youtube">https://accounts.google.com/o/oauth2/v2/auth?client_id=YOUR_CLIENT_ID&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/youtube</a>
<ul>
<li>Remember to substitute your actual client_id</li>
<li>Add any other scopes you might want</li>
<li>Go through the flow steps - it&rsquo;ll warn you that the app is unreleased, which
is expected</li>
</ul>
</li>
<li>Take the code that it provides</li>
<li>Call the following curl command</li>
</ul>
```bash
curl -X POST https://oauth2.googleapis.com/token \
  -d client_id=YOUR_CLIENT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d code=PASTE_THE_CODE_HERE \
  -d grant_type=authorization_code \
  -d redirect_uri=urn:ietf:wg:oauth:2.0:oob
```
<p>You should finally get something like:</p>
```json
{
  "access_token": "ya29...",
  "expires_in": 3599,
  "refresh_token": "1//0g...",
  "scope": "https://www.googleapis.com/auth/youtube",
  "token_type": "Bearer"
}
```
<p>The <code>refresh_token</code> is what you want to save / use as the <code>access_token</code> will
expire (after an hour in this example).</p>
<p>The authentication was a bit more involved with a refresh token, but the
<code>oauth2</code> library helps us out:</p>
```go
func NewYouTube(ClientId string, ClientSecret string, RefreshToken string) (YouTube, error) {

	conf := &oauth2.Config{
		ClientID:     ClientId,
		ClientSecret: ClientSecret,
		Endpoint:     google.Endpoint,
		Scopes:       []string{"https://www.googleapis.com/auth/youtube"},
	}

	// Construct a token from just the refresh token
	token := &oauth2.Token{RefreshToken: RefreshToken}

	ctx := context.Background()

	// Create an authenticated client
	httpClient := conf.Client(ctx, token)

	ytService, err := youtube.NewService(ctx, option.WithHTTPClient(httpClient))
	if err != nil {
		return YouTube{}, err
	}

	return YouTube{
		service: ytService,
	}, nil

}
```
<h2 id="updating-the-description">Updating the description</h2>
<p>Setting the description is a little more complicated because you can&rsquo;t set just
the description.</p>
<p>Everything defined in the <code>VideoSnippet</code> gets updated.</p>
<p>To support this, what we need to do is get the current snippet for the video,
then update it:</p>
```go
vListCall := ytService.Videos.List([]string{"snippet"})
vListCall = vListCall.Id(videoId)
res, err := vListCall.Do()
if err != nil {
    return err
}

if len(res.Items) != 1 {
    return fmt.Errorf("wrong number of videos returned: %d", len(res.Items))
}

ytVideo := res.Items[0]
ytVideo.Snippet.Description = desc

vUpdateCall := ytService.Videos.Update([]string{"snippet"}, ytVideo)
_, err = vUpdateCall.Do()
```
<h2 id="github-action">GitHub Action</h2>
<p>The GitHub Action is fairly straightforward, mostly a copy of the Hugo one,
then:</p>
<ul>
<li>Add Bazel</li>
<li>Run projector sync</li>
<li>Commit if changed</li>
</ul>
```yaml
- uses: bazel-contrib/setup-bazel@0.15.0
  with:
    # Avoid downloading Bazel every time.
    bazelisk-cache: true
    # Store build cache per workflow.
    disk-cache: ${{ github.workflow }}
    # Share repository cache between workflows.
    repository-cache: true
- name: Run projector sync
  env:
    GOOGLE_CLIENT_ID: ${{ secrets.PROJECTOR_GOOGLE_CLIENT_ID }}
    GOOGLE_CLIENT_SECRET: ${{ secrets.PROJECTOR_GOOGLE_CLIENT_SECRET }}
    GOOGLE_REFRESH_TOKEN: ${{ secrets.PROJECTOR_GOOGLE_REFRESH_TOKEN }}
  continue-on-error: true
  run:
    bazel run //tools/projector:projector -- sync -source blog/content/youtube
    -rendered blog/public/youtube
- name: Commit and push if changed
  run: |
    git config user.name "drone-ah bot"
    git config user.email "github.actions@drone-ah.com"

    if ! git diff --quiet; then
      git add -u
      git commit -m "auto: log youtube updates"
      git push
    else
      echo "No changes to commit"
    fi
```
<p>We also needed to upgrade one permission - <code>contents</code></p>
```yaml
permissions:
  contents: write
```
<h2 id="conclusion">Conclusion</h2>
<p>The Google/YouTube documentation was the hardest part here in that it was pretty
obtuse and hard to understand.</p>
<p>Writing a little frontmatter library was unexpected, but while it took a little
time, was straightforward.</p>
<p>Once I got a handle on that, the rest of it was pretty straightforward, partly
because I was reusing parts from before.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://icle.es/projector-Hugo.md">Part 1: Outputting YouTube Descriptions from Hugo</a></li>
<li><a href="https://icle.es/golang/inscribe.md">inscribe: simple frontmatter yaml library</a></li>
</ul>
]]></content:encoded></item><item><title>Inscribe: Updating frontmatter in-place with Go and yaml.Node</title><link>https://icle.es/2025/07/05/inscribe-updating-frontmatter-in-place-with-go-and-yaml.node/</link><pubDate>Sat, 05 Jul 2025 10:44:02 +0100</pubDate><guid>https://icle.es/2025/07/05/inscribe-updating-frontmatter-in-place-with-go-and-yaml.node/</guid><description>&lt;p>While building
&lt;a href="https://icle.es/wordsonsand/projector-sync.md">a little tool to sync up YouTube video descriptions from hugo&lt;/a>,
I needed a library to read and write frontmatter in yaml.&lt;/p>
&lt;p>I started with &lt;a href="https://github.com/adrg/frontmatter">adrg/frontmatter&lt;/a> before I
realised that it didn&amp;rsquo;t have the ability to write back.&lt;/p>
&lt;p>I considered contributing to that one, but writing back is a little more complex
than reading - particularly because the &lt;code>frontmatter.Parse&lt;/code> in that one is built
to support partial reading.&lt;/p>
&lt;p>Because adrg/frontmatter only unmarshals into a struct, and doesn’t store the
original bytes, you can’t write back without losing untouched keys.&lt;/p></description><content:encoded><![CDATA[<p>While building
<a href="https://icle.es/wordsonsand/projector-sync.md">a little tool to sync up YouTube video descriptions from hugo</a>,
I needed a library to read and write frontmatter in yaml.</p>
<p>I started with <a href="https://github.com/adrg/frontmatter">adrg/frontmatter</a> before I
realised that it didn&rsquo;t have the ability to write back.</p>
<p>I considered contributing to that one, but writing back is a little more complex
than reading - particularly because the <code>frontmatter.Parse</code> in that one is built
to support partial reading.</p>
<p>Because adrg/frontmatter only unmarshals into a struct, and doesn’t store the
original bytes, you can’t write back without losing untouched keys.</p>
<p>Looking around, I could not find another frontmatter library for Go. I know that
python has a decent library which supports writing back to it (I used it in the
<a href="https://icle.es/wordsonsand/despatches.md">depatcher</a> but I don&rsquo;t want to write python.</p>
<h2 id="multiple-formats">Multiple Formats</h2>
<p>Let&rsquo;s make it extendable by defining a <code>Format</code> that will allow us to add in
other formats later:</p>
```go
// A Format knows how to (un)marshal a particular format of frontmatter.
// e.g. yaml, toml etc.
type Format struct {
	// TODO: We could add details like delimiter to support other formats
	// and auto detection of format
	Unmarshal UnmarshalFunc
	Marshal   MarshalFunc
}

var yamlFormat = Format{
	Unmarshal: yaml.Unmarshal,
	Marshal:   yaml.Marshal,
}
```
<h2 id="writing-back">Writing Back</h2>
<p>We could also do with a struct to hold the whole file contents so that we can
write it back easier.</p>
```go
// A Scribed is a representation of a file that contains frontmatter and markdown content
type Scribed struct {
	format      Format
	frontmatter []byte
	Content     string
}
```
<p>By storing the full frontmatter, we can later accept partial updates without
losing other keys.</p>
<h2 id="naive-merging-of-updates">Naive Merging of Updates</h2>
<p>We (the user) should be able to update just the keys we care about. All the
other keys should be preserved.</p>
<p>The easiest way I could find to do this was to Marshal, Unmarshall and then
merge with the raw Unmarshall:</p>
<h3 id="minimal-merge-strategy-loses-order-and-formatting">Minimal merge strategy (loses order and formatting)</h3>
```go
// Merge frontmatter
var raw map[string]any // full unmarshalled frontmatter
err := s.format.Unmarshal(s.frontmatter, &raw)

updatedBytes, _ := yaml.Marshal(fm) // convert updated to yaml

var updates map[string]any
yaml.Unmarshal(updatedBytes, &updates) // get updated keys as map

for k, v := range updates {
    raw[k] = v // overwrite only touched fields
}

// raw is now the preserved + updated keys
data, err := s.format.Marshal(raw)
if err != nil {
    return err
}
```
<blockquote>
<p>⚠️ <strong>Warning</strong>: Key ordering is lost</p>
<p>Due to the way maps work, the key ordering is lost More accurately, the keys
are sorted alphabetically during write.</p>
<p>It fully rewrites the frontmatter, which also means that double quotes might
disappear etc.</p></blockquote>
<h2 id="in-place-merging-of-updates">In Place Merging of Updates</h2>
<p>If it is important to keep the frontmatter formatting as much as possible, we
need to bigger sledgehammer.</p>
<p>I explored an approach using yaml.Node with ChatGPT&rsquo;s help.</p>
<p>I fitted it into the <code>Format</code> as well</p>
```go
type MergeFunc func(raw []byte, fm any) ([]byte, error)

type Format struct {
	// TODO: We could add details like delimiter to support other formats
	// and auto detection of format
	Unmarshal UnmarshalFunc
	Marshal   MarshalFunc
	Merge     MergeFunc
}

func MergeYaml(raw []byte, fm any) ([]byte, error) {
	var node yaml.Node
	if err := yaml.Unmarshal(raw, &node); err != nil {
		return nil, err
	}

	// Expecting a document with a single mapping node
	if len(node.Content) == 0 || node.Content[0].Kind != yaml.MappingNode {
		return nil, errors.New("invalid frontmatter")
	}

	b, err := yaml.Marshal(fm)
	if err != nil {
		return nil, err
	}

	var updates map[string]string
	yaml.Unmarshal(b, &updates)

	m := node.Content[0]
	for key, value := range updates {
		// Search for the key and update it, or append a new one
		found := false
		for i := 0; i < len(m.Content); i += 2 {
			k := m.Content[i]
			if k.Value == key {
				m.Content[i+1].Value = value
				found = true
				break
			}
		}
		if !found {
			m.Content = append(m.Content,
				&yaml.Node{Kind: yaml.ScalarNode, Value: key},
				&yaml.Node{Kind: yaml.ScalarNode, Value: value},
			)
		}
	}

	var buf bytes.Buffer
	enc := yaml.NewEncoder(&buf)
	if err := enc.Encode(&node); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}
```
<p>This preserves frontmatter formatting well - but it only handles flat YAML.
Nested maps require a bit more work.</p>
<h2 id="supporting-maps-etc-as-values">Supporting maps etc. as values</h2>
<p>We need to switch to <code>map[string]any</code> and tweak a bit of the loop</p>
```go
var updates map[string]any
yaml.Unmarshal(b, &updates)

m := node.Content[0]
for key, value := range updates {
    // Encode value to a yaml.Node
    valNode := &yaml.Node{}
    if err := valNode.Encode(value); err != nil {
        return nil, err
    }

    // Search and replace or append
    found := false
    for i := 0; i < len(m.Content); i += 2 {
        if m.Content[i].Value == key {
            m.Content[i+1] = valNode
            found = true
            break
        }
    }
    if !found {
        m.Content = append(m.Content,
            &yaml.Node{Kind: yaml.ScalarNode, Value: key},
            valNode,
        )
    }
}
```
<p>It&rsquo;s funny how things can be more complicated than it first seems.</p>
<h2 id="minor-tweaks">Minor tweaks</h2>
<p>I also wanted to add the delimiter into the format and use that instead of
hardcoding, which was easy enough.</p>
```go
// A Format knows how to (un)marshal a particular format of frontmatter.
// e.g. yaml, toml etc.
type Format struct {
	Delimiter string
	Unmarshal UnmarshalFunc
	Marshal   MarshalFunc
	Merge     MergeFunc
}

var yamlFormat = Format{
	Delimiter: "---",
	Unmarshal: yaml.Unmarshal,
	Marshal:   yaml.Marshal,
	Merge:     MergeYaml,
}

func (s *Scribed) Write(fm any, out io.Writer) error {
	// Merge frontmatter
	data, err := s.format.Merge(s.frontmatter, fm)
	if err != nil {
		return err
	}

	io.WriteString(out, s.format.Delimiter+"\n")
	out.Write(data)
	io.WriteString(out, s.format.Delimiter+"\n\n")
	io.WriteString(out, s.Content)

	return nil
}

// splitFrontmatter will split frontmatter from Content and store them
func (s *Scribed) splitFrontmatter(r io.Reader) error {
	data, err := io.ReadAll(r)
	if err != nil {
		return err
	}

	parts := bytes.SplitN(data, []byte("\n"+s.format.Delimiter+"\n"), 2)
	if len(parts) != 2 {
		return errors.New("invalid frontmatter format")
	}

	// Remove the opening '---\n' from the first part
	s.frontmatter = bytes.TrimPrefix(parts[0], []byte("---\n"))

	s.Content = string(bytes.TrimLeft(parts[1], "\r\n"))

	return nil
}
```
<h2 id="conclusion">Conclusion</h2>
<p>This is probably the main bits of functionality I&rsquo;ll need to continue with the
<a href="https://icle.es/wordsonsand/projector-sync.md">projector sync</a>.</p>
<p>I had considered <code>frontmatter</code> to be a blackbox with complicated functionality,
but cutting it up and working on it has demystified it and made it easier to
work with. ChatGPT helped.</p>
<p>It should be fairly straightforward to add other frontmatter formats like TOML,
and to autodetect the formats, but I don&rsquo;t need it right now.</p>
<h2 id="links">Links</h2>
<ul>
<li><a href="https://github.com/drone-ah/wordsonsand/tree/main/lib/inscribe">Source Code on GitHub</a></li>
</ul>
]]></content:encoded></item><item><title>Build and push container image using bazel</title><link>https://icle.es/2025/06/06/bazel-go-docker-gcloud/</link><pubDate>Fri, 06 Jun 2025 13:06:14 +0000</pubDate><guid>https://icle.es/2025/06/06/bazel-go-docker-gcloud/</guid><description>&lt;p>I am building a small &lt;a href="https://icle.es/tags/golang">golang&lt;/a> &lt;a href="https://icle.es/tags/webapp">webapp&lt;/a>, and I want
to push a &lt;a href="https://icle.es/tags/oci">container&lt;/a> up for it, which can eventually be used in
Google Cloud Run, or elsewhere.&lt;/p>
&lt;p>In this post, I want to describe how I got it to push images build locally up to
googles artifact repository. It will include creating the artifac repository
using infrastructure as code&lt;/p>
&lt;p>The project is in a &lt;a href="https://icle.es/tags/monorepo">monorepo&lt;/a> that uses &lt;a href="https://icle.es/tags/bazel">bazel&lt;/a>.&lt;/p>
&lt;h2 id="the-executable-to-be-packaged">The executable to be packaged&lt;/h2>
```starlark
# //products/example/cmd/server/BUILD.bazel
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")

go_library(
 name = "server_lib",
 srcs = ["main.go"],
 visibility = ["//visibility:private"],
)

go_binary(
 name = "server",
 embed = [":server_lib"],
 visibility = ["//visibility:public"],
)
```</description><content:encoded><![CDATA[<p>I am building a small <a href="https://icle.es/tags/golang">golang</a> <a href="https://icle.es/tags/webapp">webapp</a>, and I want
to push a <a href="https://icle.es/tags/oci">container</a> up for it, which can eventually be used in
Google Cloud Run, or elsewhere.</p>
<p>In this post, I want to describe how I got it to push images build locally up to
googles artifact repository. It will include creating the artifac repository
using infrastructure as code</p>
<p>The project is in a <a href="https://icle.es/tags/monorepo">monorepo</a> that uses <a href="https://icle.es/tags/bazel">bazel</a>.</p>
<h2 id="the-executable-to-be-packaged">The executable to be packaged</h2>
```starlark
# //products/example/cmd/server/BUILD.bazel
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library", "go_test")

go_library(
    name = "server_lib",
    srcs = ["main.go"],
    visibility = ["//visibility:private"],
)

go_binary(
    name = "server",
    embed = [":server_lib"],
    visibility = ["//visibility:public"],
)
```
<h2 id="build-container-image">Build Container Image</h2>
<h3 id="add-rules_oci">add <code>rules_oci</code></h3>
<p><a href="https://github.com/bazel-contrib/rules_oci">rules_oci</a> is a good place to start
to integrate container support into your bazel configuration. It&rsquo;s also worth
<a href="https://github.com/GoogleContainerTools/distroless">reading up a bit on distroless</a>
if you are not aware of it.</p>
<p>Adding <code>rules_oci</code> into bazel <code>MODULES</code> is straightforward:</p>
```starlark
bazel_dep(name = "rules_oci", version = "2.2.6")
# For testing, we also recommend https://registry.bazel.build/modules/container_structure_test

oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")

# Declare external images you need to pull, for example:
oci.pull(
    name = "distroless_base",
    # 'latest' is not reproducible, but it's convenient.
    # During the build we print a WARNING message that includes recommended 'digest' and 'platforms'
    # values which you can use here in place of 'tag' to pin for reproducibility.
    tag = "latest",
    image = "gcr.io/distroless/base",
    platforms = ["linux/amd64"],
)

# For each oci.pull call, repeat the "name" here to expose them as dependencies.
use_repo(oci, "distroless_base")
```
<p>If you use the <code>WORKSPACE</code>, or if you want the latest version, you
<a href="https://github.com/bazel-contrib/rules_oci/releases">can find details on their releases page</a>.</p>
<h3 id="tarbzl"><code>tar.bzl</code></h3>
<p><code>oci_rules</code> uses tar files to build the image. To build tar images, you will
want to use <a href="https://github.com/bazel-contrib/tar.bzl">tar.bzl</a>.</p>
<p>Add the following to you <code>MODULES</code></p>
```starlark
bazel_dep(name = "tar.bzl", version = "0.3.0")
```
<p>Latest version and instructions for <code>WORKSPACE</code>
<a href="https://github.com/bazel-contrib/tar.bzl/releases/tag/v0.3.0">can be found on their releases page</a></p>
<h3 id="buildbazel"><code>BUILD.bazel</code></h3>
<p>There are
<a href="https://github.com/bazel-contrib/rules_oci?tab=readme-ov-file#usage">language specific sample build files</a>
that you can start from.</p>
<p>Starting
<a href="https://github.com/aspect-build/bazel-examples/blob/main/oci_go_image/BUILD.bazel">from the example go <code>BUILD.bazel</code></a>,
and simplifying it, I have:</p>
```starlark
# //products/example/deploy/BUILD.bazel
load("@rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load", "oci_push")
load("@tar.bzl", "mutate", "tar")

# Put app go_binary into a tar layer.
tar(
    name = "app_layer",
    srcs = ["//products/muster/cmd/server:server"],
    out = "app_layer.tar",
    mutate = mutate(strip_prefix = package_name() + "/app_"),
)


oci_image(
    name = "image",
    # This is defined by an oci.pull() call in /MODULE.bazel
    base = "@distroless_base",
    entrypoint = ["/app"],
    # Link the resulting image back to the repository where the build is defined.
    labels = {
        "org.opencontainers.image.source": "https://github.com/aspect-build/bazel-examples",
    },
    tars = [":app_layer"],
)
```
<p>There is
<a href="https://github.com/bazel-contrib/rules_oci/blob/main/docs/go.md">more information at the go doc page</a>
including details of migrating from
<a href="https://github.com/bazelbuild/rules_docker"><code>rules_docker</code></a></p>
```bash
products/example/deploy $ bazel build ...
INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).
INFO: Found 2 targets...
INFO: Elapsed time: 0.444s, Critical Path: 0.07s
INFO: 2 processes: 1 internal, 1 linux-sandbox.
INFO: Build completed successfully, 2 total actions
```
<p>Building of the images now completes</p>
<h2 id="push-image">Push Image</h2>
<p>Pushing the image can be pretty straightforward:</p>
<ul>
<li>Enable the container repository in <a href="https://icle.es/tags/google-cloud">Google Cloud</a></li>
<li>Configure it in the <code>BUILD.bazel</code> file</li>
<li><code>bazel run</code> to push</li>
</ul>
<p>I would like to do the whole thing through <a href="https://icle.es/tags/iac">IaC</a> so that it is</p>
<ul>
<li>Repeatable, and</li>
<li>more importantly documented</li>
</ul>
<h3 id="iac-tool-options">IaC Tool Options</h3>
<p><a href="https://developer.hashicorp.com/terraform">terraform</a> has no bazel support, and
is a questionable choice from an ethics perspective. <a href="https://icle.es/">opentofu</a> has</p>
<p><a href="https://github.com/yanndegat/rules_tf">rules_tf</a>. However, that
<a href="https://github.com/yanndegat/rules_tf/issues/5">does not support <code>apply</code></a></p>
<p><a href="https://cloud.google.com/infrastructure-manager/docs">Google Cloud Infrastucture Manager</a>
is vendor lock in.</p>
<p>That leaves us with <a href="https://www.pulumi.com/">pulumi</a> (If there is another
alternative, please let me know).</p>
<p>I like the stacks concept in pulumi, and while it doesn&rsquo;t have bazel
integration, what it does have is an
<a href="https://github.com/pulumi/automation-api-examples">automation api</a>. While not
he best documented, there is enough documentation out there to be able to piece
it together.</p>
<p>With this, we can build a runnable unit, and then run it too. I&rsquo;ve experimented
with tagging specific runnables and then running them through the CI. I won&rsquo;t
get that far in this post. My focus here is to get it working locally.</p>
<h3 id="enable-artifact-repository">Enable Artifact Repository</h3>
<p>In pulumi, you can enable the artifact repository with:</p>
```go

func enableArtifactRepository(ctx *pulumi.Context) error {
	// Set your GCP project and region
	projectID := "<projectId>"
	region := "europe-west1"
	repoName := "<repo-name>"

	// Create Artifact Registry repository
	repo, err := artifactregistry.NewRepository(ctx, repoName, &artifactregistry.RepositoryArgs{
		Format:       pulumi.String("DOCKER"),
		Location:     pulumi.String(region),
		RepositoryId: pulumi.String(repoName),
		Description:  pulumi.String("Repository for OCI container images"),
		Project:      pulumi.String(projectID),
	})
	if err != nil {
		return err
	}

	// Export the repository URL (used for pushes)
	ctx.Export("repositoryURL", pulumi.Sprintf("%s-docker.pkg.dev/%s/%s", region, projectID, repo.Name))

	return nil

}
```
<h3 id="authenticating-against-the-repo">Authenticating against the repo</h3>
<p>You need to authenticate against the repository so that the <code>oci_rule</code> can pick
it up</p>
```bash
gcloud auth configure-docker europe-west1-docker.pkg.dev
```
<h3 id="define-the-push-target">Define the push target</h3>
```starlark
oci_push(
    name = "push",
    image = ":image",
    remote_tags = [
        "latest",
        "1h",
    ],
    repository = "europe-west1-docker.pkg.dev/<projectId>/<repo-name>/<image-name>",
)
```
<h2 id="pushing">Pushing</h2>
<p>Once all the above is set up, you can then push the image with one of the
following:</p>
```bash
bazel run :push # if you in the directory
bazel run //<target/path>:push # fron anywhere in the repo
```
<h2 id="side-note">Side note</h2>
<p>Please do not take my heavy usage of google and its products to be a fan letter
or an endorsement. They <em>might</em> at best, be the lesser evil.</p>]]></content:encoded></item><item><title>How to run your lambda code locally as its role (for testing)</title><link>https://icle.es/2023/11/14/automated-testing-of-aws-lambda-locally/</link><pubDate>Tue, 14 Nov 2023 14:23:37 +0000</pubDate><guid>https://icle.es/2023/11/14/automated-testing-of-aws-lambda-locally/</guid><description>&lt;p>While AWS Lambda is fantastic in providing a serverless platform with few
worries about maintaining servers, it is not the easiest to test in an automated
fashion with rapid feedback.&lt;/p>
&lt;p>You could write end to end tests, but it means a deployment after each change
and then checking the logs to see what failed. Even if you use iac
(terraform/pulumi), the deployment will take seconds or a minute or two - not
exact rapid test feedback.&lt;/p>
&lt;p>What I have been doing is to set up a hook which is called from the lambda
handler, which can also be called locally. Within the test, I then assume the
role that runs the lambda and then test the hook.&lt;/p>
&lt;p>This mechanism allows to me easily test that the permissions are set up
correctly and that details are in place for the code to work.&lt;/p>
&lt;p>For the full end to end test, I then have a simple smoke test or two.&lt;/p>
&lt;p>The code samples are in golang(only because it happens to be my current language
of choice), but the idea should be equally applicable in other languages.&lt;/p></description><content:encoded><![CDATA[<p>While AWS Lambda is fantastic in providing a serverless platform with few
worries about maintaining servers, it is not the easiest to test in an automated
fashion with rapid feedback.</p>
<p>You could write end to end tests, but it means a deployment after each change
and then checking the logs to see what failed. Even if you use iac
(terraform/pulumi), the deployment will take seconds or a minute or two - not
exact rapid test feedback.</p>
<p>What I have been doing is to set up a hook which is called from the lambda
handler, which can also be called locally. Within the test, I then assume the
role that runs the lambda and then test the hook.</p>
<p>This mechanism allows to me easily test that the permissions are set up
correctly and that details are in place for the code to work.</p>
<p>For the full end to end test, I then have a simple smoke test or two.</p>
<p>The code samples are in golang(only because it happens to be my current language
of choice), but the idea should be equally applicable in other languages.</p>
<h1 id="assuming-the-role">Assuming The Role</h1>
```go
  roleToAssume := os.Getenv("AUTH_LAMBDA_ROLE_ARN")

    ctx := context.TODO()
    cfg, err := config.LoadDefaultConfig(ctx)

    if err != nil {
        logger.Fatal("error: ", err)
    }
    // Create the credentials from AssumeRoleProvider to assume the role
    // referenced by the "myRoleARN" ARN using the MFA token code provided.
    creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), roleToAssume)

    logger.Debugf("creds: %v", creds)

    cfg.Credentials = aws.NewCredentialsCache(creds)
```
<p>the cfg is then passed into the <code>New</code> method for the resource you are interested
in. e.g.:</p>
```go
   ssmClient := ssm.NewFromConfig(cfg)
```
<h1 id="working-example">Working Example</h1>
<p>You can find a full, working example test in
<a href="https://github.com/drone-ah/wordsonsand">my github repo</a> under
<a href="https://github.com/drone-ah/wordsonsand/tree/main/post/2023/11/autolambdatest">post/2023/11/autolambdatest</a></p>
<p>NOTE: It WILL automatically try and deploy a role and a ssm parameter, and it
will delete it after the test.</p>
<p>The <code>BeforeSuite</code> will deploy the minimum configuration to be able to run the
test, and the <code>AfterSuite</code> will destroy the same stack.</p>
<p>You will likely need to log into pulumi to get this test to work.</p>
<p>If you run into permissions issue for AssumeRole, read on.</p>
<h1 id="assumerole-permissions">AssumeRole Permissions</h1>
<p>For this to work, the user running the tests need to have permissions to
<code>AssumeRole</code>.</p>
<p>There are two steps to this. The first part is to allow &quot;anyone&quot; to
<code>AssumeRole</code> the relevant role:</p>
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::<your-account-id>:root"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```
<p>This will allow &quot;any user&quot; to assume the role, as long as they have the
permission to do so.</p>
<p><code>arn:aws:iam::[your-account]:root</code> is a special user the represents the account
(and the non-IAM root user). Since IAM (user, roles etc.) exists under the
&quot;root&quot; account, all calls are also authenticated by this account - i.e. all
users, roles etc. in IAM is also this account. There is
<a href="https://www.reddit.com/r/aws/comments/oorjl2/what_exactly_is_the_root_iam_principal/">a post on reddit discussing what exactly the root iam principal is for more information</a></p>
<p>Finally, unless you have the <code>Administrator</code>Access policy set against your
account, you will also need to attach a policy to the relevant group (or your
user) that grants permissions to call <code>sts:AssumeRole</code> (or <code>*</code>)</p>
```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "123",
      "Effect": "Allow",
      "Action": ["sts:AssumeRole"],
      "Resource": ["arn:aws:iam::123456789012:role/desired-role"]
    }
  ]
}
```
<p>You can of course, also use <code>*</code> for Resource above to allow the user/group to
Assume Any role. In practice, you might want to automate this as part of the
creation of the relevant roles. (i.e. create the role, then give the relevant
group permissions to Assume that role).</p>]]></content:encoded></item><item><title>Separating out integration tests for golang in Bazel</title><link>https://icle.es/2023/11/13/separating-out-integration-tests-for-golang-in-bazel/</link><pubDate>Mon, 13 Nov 2023 13:47:53 +0000</pubDate><guid>https://icle.es/2023/11/13/separating-out-integration-tests-for-golang-in-bazel/</guid><description>&lt;p>There are many kinds of automated tests and two main kinds are integration tests
and unit tests.&lt;/p>
&lt;p>Unit tests are designed to run as fast as possible, so any slower processes like
databases are mocked out. While super helpful and powerful in terms of providing
confidence in the software, it should be only one part of the testing strategy.&lt;/p>
&lt;p>Integration tests, as is implied runs tests of the different part of the
software integrated together. Technically speaking, you can still mock out the
database and other slower layers to keep it running quickly. However, there is
value in including a database or other slower services in the process to test as
them in an automated fashion.&lt;/p>
&lt;p>What this does mean though, is that you want to be able to run only the unit
tests or run the integration tests as well. You might also want to have smoke
tests, which are run on your live production environment.&lt;/p></description><content:encoded><![CDATA[<p>There are many kinds of automated tests and two main kinds are integration tests
and unit tests.</p>
<p>Unit tests are designed to run as fast as possible, so any slower processes like
databases are mocked out. While super helpful and powerful in terms of providing
confidence in the software, it should be only one part of the testing strategy.</p>
<p>Integration tests, as is implied runs tests of the different part of the
software integrated together. Technically speaking, you can still mock out the
database and other slower layers to keep it running quickly. However, there is
value in including a database or other slower services in the process to test as
them in an automated fashion.</p>
<p>What this does mean though, is that you want to be able to run only the unit
tests or run the integration tests as well. You might also want to have smoke
tests, which are run on your live production environment.</p>
<h1 id="how">How</h1>
<p>You could define a separate target in your <code>BUILD</code> file with the unit tests and
let <code>gazelle</code> automatically build your default test target with all the tests. I
found this frustrating to use as I had to keep tweaking the dependencies
manually whenever anything changed (which happened often)</p>
<h2 id="tagging">Tagging</h2>
<p>The easiest way to achieve this for golang and bazel is to tag your source code
files. You can do this by adding the following to the top of your integration
test files</p>
<p><code>something_integration_test.go</code></p>
```go
//go:build integration_test

package somepackage
```
<p>You can pick any tag name you want instead of <code>integration_test</code> like
<code>integration</code>, <code>smoke_test</code> etc.</p>
<h2 id="ide-support">IDE Support</h2>
<p>You will likely need to add this source file into the IDEs build constraints to
get the IDE to treat it as a source file. In IntelliJ (IDEA/Goland), you will be
warned of this</p>
<p>
  <img src="/assets/2023/11/image.png" alt="idea screenshot, main_test.go is ignored by build tool">

</p>
<p>If you click <code>Edit settings</code>, you can add the tag in</p>
<p>
  <img src="/assets/2023/11/image-1.png" alt="set custom tags in build tags">

</p>
<p>When running <code>gazelle</code>, you want to include the files with these tags</p>
<h2 id="gazelle">Gazelle</h2>
```bash
bazel run //:gazelle -- -build_tags=integration_test
```
<p>If you have multiple tags, you can separate them with commas. This command will
generate a test target with all of the source files and its dependencies</p>
<h2 id="bazel-integration-on-test">Bazel Integration on test</h2>
<p>To run only the unit tests, you test as normal:</p>
```bash
bazel test ... # or the specific target, and it'll run only the unit tests
```
<p>To run the integration tests as well, include that tag</p>
```bash
bazel test ... --define  gotags=integration_test # Will run unit & integration tests
```
<h2 id="run-only-unit-tests">Run only Unit Tests</h2>
<p>This setup will currently not allow you to run ONLY the integration tests. To be
able to do that you'll need to add a <code>unit_test</code> tag to the unit test files so
that you can exclude them.</p>
```
something_test.go
```
```go
//go:build integration_test

package somepackage
```
<p>You can then run only the unit tests with</p>
```bash
bazel test … --define gotags=unit_test # Will run unit & integration tests
```
<p>Only the integration tests</p>
```bash
bazel test … --define gotags=integration_test # Will run unit & integration tests
```
<p>Or both:</p>
```bash
bazel test … --define gotags=unit_test,integration_test # Will run unit & integration tests
```
<h2 id="simpler-gazelle-command">Simpler gazelle command</h2>
<p>You can enable the tags by default in the <code>BUILD</code> file so that you don't have
to pass the tags into gazelle each time.</p>
<p><code>BUILD</code></p>
```starlark
# gazelle:build_tags unit_test,integration_test
```
<p>You can then just run <code>bazel run //:gazelle</code> which will run with these tags
enabled.</p>
<h1 id="sample-source">Sample Source</h1>
<p>You can find sample source code demonstrating this in
<a href="https://github.com/drone-ah/wordsonsand">my github repo</a>, under
<a href="https://github.com/drone-ah/wordsonsand/tree/main/post/2023/11/separatetests">post/2023/11/separatetests</a></p>]]></content:encoded></item><item><title>Streaming Progress With Pulumi Automation API</title><link>https://icle.es/2023/11/08/streaming-progress-with-pulumi-automation-api/</link><pubDate>Wed, 08 Nov 2023 12:38:32 +0000</pubDate><guid>https://icle.es/2023/11/08/streaming-progress-with-pulumi-automation-api/</guid><description>&lt;p>When using the pulumi automation API, you lose some of the niceties of the
pulumi CLI, like having to set up command line args processing and the output is
not as friendly or pretty as before. It also doesn't stream the output - though
this one is easier to fix.&lt;/p>
&lt;p>This is lifted straight out of
&lt;a href="https://github.com/pulumi/automation-api-examples/blob/3114b754ea84ebd0cc1e1b67f128df75795bd4c3/go/local_program/automation/main.go#L74C2-L82C3">their golang example code&lt;/a>,
so if you're working in another language - you should be able to find the
relevant code in the same repo&lt;/p></description><content:encoded><![CDATA[<p>When using the pulumi automation API, you lose some of the niceties of the
pulumi CLI, like having to set up command line args processing and the output is
not as friendly or pretty as before. It also doesn't stream the output - though
this one is easier to fix.</p>
<p>This is lifted straight out of
<a href="https://github.com/pulumi/automation-api-examples/blob/3114b754ea84ebd0cc1e1b67f128df75795bd4c3/go/local_program/automation/main.go#L74C2-L82C3">their golang example code</a>,
so if you're working in another language - you should be able to find the
relevant code in the same repo</p>
```go
   // wire up our update to stream progress to stdout
    stdoutStreamer := optup.ProgressStreams(os.Stdout)

    // run the update to deploy our fargate web service
    res, err := stack.Up(ctx, stdoutStreamer)
    if err != nil {
        fmt.Printf("Failed to update stack: %v\n\n", err)
    }
```
]]></content:encoded></item><item><title>How to get current Function URL (aws-lambda + golang)</title><link>https://icle.es/2023/11/08/how-to-get-current-function-url-aws-lambda-golang/</link><pubDate>Wed, 08 Nov 2023 12:10:16 +0000</pubDate><guid>https://icle.es/2023/11/08/how-to-get-current-function-url-aws-lambda-golang/</guid><description>&lt;p>When deploying a function lambda that needs details of its own function URL.
It's an OAuth Callback, and needs to calculate the redirect. There are possible
security issues doing it this way, so will switch to http gateway on launch. In
the meantime, though, I ran into a bit of a chicken and egg problem.&lt;/p>
&lt;p>In Pulumi, the function URL is created after the function (and even otherwise),
I can't pass the output of the lambda (or lambdaFunctionUrl) back in as an
environment variable. Fortunately, there is an easy way to pick up the Function
URL (or the function name for that matter) - if you know how ;)&lt;/p></description><content:encoded><![CDATA[<p>When deploying a function lambda that needs details of its own function URL.
It's an OAuth Callback, and needs to calculate the redirect. There are possible
security issues doing it this way, so will switch to http gateway on launch. In
the meantime, though, I ran into a bit of a chicken and egg problem.</p>
<p>In Pulumi, the function URL is created after the function (and even otherwise),
I can't pass the output of the lambda (or lambdaFunctionUrl) back in as an
environment variable. Fortunately, there is an easy way to pick up the Function
URL (or the function name for that matter) - if you know how ;)</p>
```wp-block-syntaxhighlighter-code
   domainName := request.RequestContext.DomainName
    funcName := os.Getenv("AWS_LAMBDA_FUNCTION_NAME")
    return fmt.Sprintf("Domain: %s, funcName: %s", domainName, funcName), nil
```
<p>There are other
<a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html#configuration-envvars-runtime">defined lambda function environment variables</a>
as well that you can use.</p>
]]></content:encoded></item><item><title>Bazel + Pulumi Automation API. Deploying an inline stack along with Pulumi.[stack].yaml</title><link>https://icle.es/2023/11/07/bazel-pulumi-automation-api-deploying-an-inline-stack-along-with-pulumi-yaml/</link><pubDate>Tue, 07 Nov 2023 15:57:52 +0000</pubDate><guid>https://icle.es/2023/11/07/bazel-pulumi-automation-api-deploying-an-inline-stack-along-with-pulumi-yaml/</guid><description>&lt;p>I believe in CI/CD/CD as in Continuous, integration, delivery and deployment. As
part of this, I am setting up a workflow where on merge to develop (or
main/trunk), the deployment is triggered automatically. Pulumi deploys the
current state of code and infrastructure through GitHub actions and
&lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services">OpenID Connect(OIDC)&lt;/a>
as part of the GitHub Action.&lt;/p>
&lt;p>I used to configure Pulumi to be triggered directly from the build process, but
bazel (as far I know), does not support Pulumi. When I used pants, there was a
custom module, developed by one of the community members which did support
pulumi (You might have to ask in the slack channel if you're interested), but
they stopped maintaining it as they moved to the Pulumi Automation API.&lt;/p></description><content:encoded><![CDATA[<p>I believe in CI/CD/CD as in Continuous, integration, delivery and deployment. As
part of this, I am setting up a workflow where on merge to develop (or
main/trunk), the deployment is triggered automatically. Pulumi deploys the
current state of code and infrastructure through GitHub actions and
<a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services">OpenID Connect(OIDC)</a>
as part of the GitHub Action.</p>
<p>I used to configure Pulumi to be triggered directly from the build process, but
bazel (as far I know), does not support Pulumi. When I used pants, there was a
custom module, developed by one of the community members which did support
pulumi (You might have to ask in the slack channel if you're interested), but
they stopped maintaining it as they moved to the Pulumi Automation API.</p>
<p>I am using Automation API from the start, and configuring a &quot;deployer&quot; per
product/project within the monorepo. The intention is for the deployer to be as
smart as possible - eventually <code>up</code>-ing only the stacks that have changes since
the last time- but that's a way down the line.</p>
<p>Another benefit from the Automation API is to pick up the stack outputs
automatically when running integration/e2e tests, making the test configuration
smoother.</p>
<p>The first step is to be able to define a stack within a product, hook it into
the main iac executable and have it working. My directory structure is roughly
as below: While I am using golang as my language of choice, it's probably not
hugely different in other languages.</p>
<ul>
<li>products
<ul>
<li>productA
<ul>
<li>auth
<ul>
<li>iac_auth
<ul>
<li>BUILD</li>
<li>deploy.go</li>
<li>Pulumi.dev.yaml</li>
<li>Pulumi.yaml</li>
</ul>
</li>
<li>OtherAuthModules</li>
</ul>
</li>
<li>iac
<ul>
<li>BUILD</li>
<li>main.go</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="iac_authbuild"><code>iac_auth/BUILD</code></h1>
```wp-block-syntaxhighlighter-code
# load etc.
go_library(
    name = "iac_auth",
    srcs = ["deploy.go"],
    data = glob([
        "Pulumi.*.yaml",
    ]) + [
        "Pulumi.yaml",
    ],
    visibility = ["//visibility:public"],
    deps = [
       # dependencies automated with gazelle
    ],
)
```
<h1 id="iac_authdeploy.go"><code>iac_auth/deploy.go</code></h1>
```wp-block-syntaxhighlighter-code
package iac_sync

import (
    "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

const moduleName = "auth"

func DeployGithubLambda(ctx *pulumi.Context) error {
    fmt.Println("Deploy")

    conf := config.Require(ctx, "key")
    fmt.Printf("conf: %s\n", conf) // Will successfully pick up the value if key is in Pulumi.<stack>.yaml

    // Actual deployment code here

    return nil
}
```
<h1 id="iacbuild"><code>iac/BUILD</code></h1>
```wp-block-syntaxhighlighter-code
# other config including standard config of :iac_lib by gazelle

go_binary(
    name = "iac",
    args = [
        "-iac_auth",
        "$(location //products/productA/auth/iac_auth)",
    ],
    data = [
        "//products/gitolink/productA/iac_auth",
    ],
    embed = [":iac_lib"],
    visibility = ["//visibility:public"],
)
```
<p>Worth noting the <code>args</code> bit, which is what we use to identify the path where the
<code>Pulumi.yaml</code> and <code>Pulumi.&lt;stack&gt;.yaml</code> files are:</p>
<h1 id="iacmain.go"><code>iac/main.go</code></h1>
```wp-block-syntaxhighlighter-code
package main

import (
    "context"
    "flag"
    "fmt"
    iacAuth "github.com/drone-ah/monorepo/products/gitolink/auth/iac_auth"
    "github.com/pulumi/pulumi/sdk/v3/go/auto"
    "path/filepath"
)

func main() {

    // Pick up the path from args
    var iacAuthPath = flag.String("iac_auth", "", "bin for iac_auth")
    flag.Parse()
    fmt.Printf("param iac: %s \n", *iacAuthPath)

    ctx := context.Background()

    projectName := "gitolink"
    stackName := "dev"

    stack, err := auto.NewStackInlineSource(ctx,
        stackName,
        projectName,
        iacAuth.DeployGithubLambda,
        // Define workdir for locatin of the Pulumi yaml files
        auto.WorkDir(filepath.Dir(*iacAuthPath)))

    preview, err := stack.Preview(ctx)

    fmt.Println(err)
    fmt.Println(preview)
}
```
<p>You might also have to use <code>RLocation</code> (see my previous post about
<a href="https://drone-ah.com/2023/11/01/including-a-built-artifact-in-another-target-bazel-golang/">including an artifact in another target</a>
for an example of this), though when I tried it, it was missing one of the yaml
files and I didn't investigate further.</p>]]></content:encoded></item><item><title>Including a built artifact in another target (Bazel, golang)</title><link>https://icle.es/2023/11/01/including-a-built-artifact-in-another-target-bazel-golang/</link><pubDate>Wed, 01 Nov 2023 19:42:30 +0000</pubDate><guid>https://icle.es/2023/11/01/including-a-built-artifact-in-another-target-bazel-golang/</guid><description>&lt;p>We use pulumi to do IaC and we use a monorepo with Bazel as the build tool. We
have out modules set out as following&lt;/p>
&lt;p>One of the requirements we have is to build a lambda module and then deploy it.
The lambda module is a target being built by Bazel (golang, but shouldn't
matter):&lt;/p>
```go
go_binary(
 name = "lambda_module",
 visibility = ["//visibility:public"],
)
```</description><content:encoded><![CDATA[<p>We use pulumi to do IaC and we use a monorepo with Bazel as the build tool. We
have out modules set out as following</p>
<p>One of the requirements we have is to build a lambda module and then deploy it.
The lambda module is a target being built by Bazel (golang, but shouldn't
matter):</p>
```go
go_binary(
    name = "lambda_module",
    visibility = ["//visibility:public"],
)
```
<p>We then have the iac module, which should get the built version of the above
module, so that it can then upload it into lambda</p>
```go
go_binary(
    name = "iac",
    args = [
        "-lambda_module",
        "$(location //products/productA/module/lambda_module)",
    ],
    data = ["//products/productA/module/lambda_module"],
    visibility = ["//visibility:public"],
)
```
<p>There are two key parameters here to note:</p>
<ul>
<li><code>args</code>: We generate the path to the target module using
<code> //products/productA/module/lambda_module)</code></li>
<li><code>data</code>: We use the data tag to ensure that the built output is included when
building/running this target</li>
</ul>
<p>We then need to use runfiles support within golang to be ablet to identify the
correct location for the built binary. The reason this part is complex is to be
able to support multiple operating systems. I should caveat that I have only got
this working on Linux, but Mac/Win shouldn't be too different.</p>
```go
package main

import (
    _ "embed"
    "flag"
    "fmt"
    "github.com/bazelbuild/rules_go/go/runfiles"
    "path/filepath"
)

func main() {

    var webhookAuth = flag.String("webhook_auth", "", "bin for webhook_auth")
    flag.Parse()
    fmt.Printf("param : %s \n", *webhookAuth)

    path, err := runfiles.Rlocation(fmt.Sprintf("workspace_name/%s", *webhookAuth))
    fmt.Printf("rLoc path: %s, err: %v \n", path, err)

    symlinks, err := filepath.EvalSymlinks(path)
    fmt.Printf("evaluated path: %s, err: %v \n", symlinks, err)

}
```
<p>We use the flag module to retrieve the path passed in as a runtime parameter</p>
<p>We use <code>runfiles.Rlocation</code> to pick up the &quot;real&quot; path to the file, prepending
the workspace name to the start. You can
<a href="https://bazel.build/rules/lib/globals/workspace#workspace">define the workspace name</a>
in the root WORKSPACE file with <code>workspace(name = &quot;workspace_name&quot;)</code></p>
<p>Finally, resolve the Symlink to get the actual file path</p>
<h2 id="references">References</h2>
<p>There are similar mechanisms to find the rLocation in other languages, a couple
of which are described in
<a href="https://docs.google.com/document/d/e/2PACX-1vSDIrFnFvEYhKsCMdGdD40wZRBX3m3aZ5HhVj4CtHPmiXKDCxioTUbYsDydjKtFDAzER5eg7OjJWs3V/pub">its design document</a></p>
<p>There is some documentation in <code>rules_go</code> around
<a href="https://github.com/bazelbuild/rules_go#how-do-i-access-go_binary-executables-from-go_test">accessing <code>go_binary</code> from <code>go_test</code></a>
which I referenced and updated to get the above example</p>
<p>I found the above link from
<a href="https://stackoverflow.com/questions/70193581/feed-bazel-output-to-another-bazel-rule">a stackoverflow post about feeding bazel output to another bazel rule</a></p>]]></content:encoded></item></channel></rss>