Compare commits

..

7 Commits

Author SHA1 Message Date
0ec56c9928 docs: add podman compose docs 2025-07-10 18:57:25 +04:00
35520cfe99 Merge pull request #3989 from Infisical/add-access-token-index
add index for referencing columns in identity access token
2025-07-10 09:48:39 -04:00
Sid
ba0f6e60e2 fix: yaml secret file parsing (#3837) 2025-07-10 15:33:59 +05:30
579c68b2a3 Merge pull request #3991 from Infisical/helm-update-v0.9.4
Update Helm chart to version v0.9.4
2025-07-10 14:03:10 +04:00
f4ea3e1c75 Update Helm chart to version v0.9.4 2025-07-10 10:02:02 +00:00
7d37ea318f Merge pull request #3990 from Infisical/daniel/operator-logs
fix: add request ID to error logs
2025-07-10 13:57:44 +04:00
5cb7ecc354 fix: update go sdk 2025-07-10 13:35:59 +04:00
7 changed files with 264 additions and 65 deletions

View File

@ -4,17 +4,20 @@ description: "Read how to run Infisical with Docker Compose template."
--- ---
This self-hosting guide will walk you through the steps to self-host Infisical using Docker Compose. This self-hosting guide will walk you through the steps to self-host Infisical using Docker Compose.
## Prerequisites
- [Docker](https://docs.docker.com/engine/install/)
- [Docker compose](https://docs.docker.com/compose/install/)
<Warning> <Tabs>
This Docker Compose configuration is not designed for high-availability production scenarios. <Tab title="Docker Compose">
It includes just the essential components needed to set up an Infisical proof of concept (POC). ## Prerequisites
To run Infisical in a highly available manner, give the [Docker Swarm guide](/self-hosting/deployment-options/docker-swarm). - [Docker](https://docs.docker.com/engine/install/)
</Warning> - [Docker compose](https://docs.docker.com/compose/install/)
## Verify prerequisites <Warning>
This Docker Compose configuration is not designed for high-availability production scenarios.
It includes just the essential components needed to set up an Infisical proof of concept (POC).
To run Infisical in a highly available manner, give the [Docker Swarm guide](/self-hosting/deployment-options/docker-swarm).
</Warning>
## Verify prerequisites
To verify that Docker compose and Docker are installed on the machine where you plan to install Infisical, run the following commands. To verify that Docker compose and Docker are installed on the machine where you plan to install Infisical, run the following commands.
Check for docker installation Check for docker installation
@ -27,55 +30,145 @@ To run Infisical in a highly available manner, give the [Docker Swarm guide](/se
docker-compose docker-compose
``` ```
## Download docker compose file ## Download docker compose file
You can obtain the Infisical docker compose file by using a command-line downloader such as `wget` or `curl`. You can obtain the Infisical docker compose file by using a command-line downloader such as `wget` or `curl`.
If your system doesn't have either of these, you can use a equivalent command that works with your machine. If your system doesn't have either of these, you can use a equivalent command that works with your machine.
<Tabs>
<Tab title="curl">
```bash
curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
```
</Tab>
<Tab title="wget">
```bash
wget -O docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
```
</Tab>
</Tabs>
## Configure instance credentials
Infisical requires a set of credentials used for connecting to dependent services such as Postgres, Redis, etc.
The default credentials can be downloaded using the one of the commands listed below.
<Tabs>
<Tab title="curl">
```bash
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
<Tab title="wget">
```bash
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
</Tabs>
Once downloaded, the credentials file will be saved to your working directly as `.env` file.
View all available configurations [here](/self-hosting/configuration/envars).
<Warning>
The default .env file contains credentials that are intended solely for testing purposes.
Please generate a new `ENCRYPTION_KEY` and `AUTH_SECRET` for use outside of testing.
Instructions to do so, can be found [here](/self-hosting/configuration/envars).
</Warning>
## Start Infisical
Run the command below to start Infisical and all related services.
<Tabs>
<Tab title="curl">
```bash ```bash
curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml docker-compose -f docker-compose.prod.yml up
``` ```
</Tab> </Tab>
<Tab title="wget"> <Tab title="Podman Compose">
```bash Podman Compose is an alternative way to run Infisical using Podman as a replacement for Docker. Podman is backwards compatible with Docker Compose files.
wget -O docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
## Prerequisites
- [Podman](https://podman-desktop.io/docs/installation)
- [Podman Compose](https://podman-desktop.io/docs/compose)
<Warning>
This Docker Compose configuration is not designed for high-availability production scenarios.
It includes just the essential components needed to set up an Infisical proof of concept (POC).
To run Infisical in a highly available manner, give the [Docker Swarm guide](/self-hosting/deployment-options/docker-swarm).
</Warning>
## Verify prerequisites
To verify that Podman compose and Podman are installed on the machine where you plan to install Infisical, run the following commands.
Check for podman installation
```bash
podman version
```
Check for podman compose installation
```bash
podman-compose version
```
## Download Docker Compose file
You can obtain the Infisical docker compose file by using a command-line downloader such as `wget` or `curl`.
If your system doesn't have either of these, you can use a equivalent command that works with your machine.
<Tabs>
<Tab title="curl">
```bash
curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
```
</Tab>
<Tab title="wget">
```bash
wget -O docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
```
</Tab>
</Tabs>
## Configure instance credentials
Infisical requires a set of credentials used for connecting to dependent services such as Postgres, Redis, etc.
The default credentials can be downloaded using the one of the commands listed below.
<Tabs>
<Tab title="curl">
```bash
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
<Tab title="wget">
```bash
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
</Tabs>
<Note>
Make sure to rename the `.env.example` file to `.env` before starting Infisical. Additionally it's important that the `.env` file is in the same directory as the `docker-compose.prod.yml` file.
</Note>
## Setup Podman
Run the commands below to setup Podman for first time use.
```bash
podman machine init --now
podman machine set --rootful
podman machine start
```
<Note>
If you are using a rootless podman installation, you can skip the `podman machine set --rootful` command.
</Note>
## Start Infisical
Run the command below to start Infisical and all related services.
```bash
podman-compose -f docker-compose.prod.yml up
``` ```
</Tab> </Tab>
</Tabs> </Tabs>
## Configure instance credentials
Infisical requires a set of credentials used for connecting to dependent services such as Postgres, Redis, etc.
The default credentials can be downloaded using the one of the commands listed below.
<Tabs>
<Tab title="curl">
```bash
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
<Tab title="wget">
```bash
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
</Tabs>
Once downloaded, the credentials file will be saved to your working directly as `.env` file.
View all available configurations [here](/self-hosting/configuration/envars).
<Warning>
The default .env file contains credentials that are intended solely for testing purposes.
Please generate a new `ENCRYPTION_KEY` and `AUTH_SECRET` for use outside of testing.
Instructions to do so, can be found [here](/self-hosting/configuration/envars).
</Warning>
## Start Infisical
Run the command below to start Infisical and all related services.
```bash
docker-compose -f docker-compose.prod.yml up
```
Your Infisical instance should now be running on port `80`. To access your instance, visit `http://localhost:80`. Your Infisical instance should now be running on port `80`. To access your instance, visit `http://localhost:80`.

View File

@ -77,3 +77,91 @@ export const parseJson = (src: ArrayBuffer | string) => {
}); });
return env; return env;
}; };
/**
* Parses simple flat YAML with support for multiline strings using |, |-, and >.
* @param {ArrayBuffer | string} src
* @returns {Record<string, { value: string, comments: string[] }>}
*/
export function parseYaml(src: ArrayBuffer | string) {
const result: Record<string, { value: string; comments: string[] }> = {};
const content = src.toString().replace(/\r\n?/g, "\n");
const lines = content.split("\n");
let i = 0;
let comments: string[] = [];
while (i < lines.length) {
const line = lines[i].trim();
// Collect comment
if (line.startsWith("#")) {
comments.push(line.slice(1).trim());
i += 1; // move to next line
} else {
// Match key: value or key: |, key: >, etc.
const keyMatch = lines[i].match(/^([\w.-]+)\s*:\s*(.*)$/);
if (keyMatch) {
const [, key, rawValue] = keyMatch;
let value = rawValue.trim();
// Multiline string handling
if (value === "|-" || value === "|" || value === ">") {
const isFolded = value === ">";
const baseIndent = lines[i + 1]?.match(/^(\s*)/)?.[1]?.length ?? 0;
const collectedLines: string[] = [];
i += 1; // move to first content line
while (i < lines.length) {
const current = lines[i];
const currentIndent = current.match(/^(\s*)/)?.[1]?.length ?? 0;
if (current.trim() === "" || currentIndent >= baseIndent) {
collectedLines.push(current.slice(baseIndent));
i += 1; // move to next line
} else {
break;
}
}
if (isFolded) {
// Join lines with space for `>` folded style
value = collectedLines.map((l) => l.trim()).join(" ");
} else {
// Keep lines with newlines for `|` and `|-`
value = collectedLines.join("\n");
}
} else {
// Inline value — strip quotes and inline comment
const commentIndex = value.indexOf(" #");
if (commentIndex !== -1) {
value = value.slice(0, commentIndex).trim();
}
// Remove surrounding quotes
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
i += 1; // advance to next line
}
result[key] = {
value,
comments: [...comments]
};
comments = []; // reset
} else {
i += 1; // skip unknown line
}
}
}
return result;
}

View File

@ -9,7 +9,7 @@ import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality // TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality
import { parseDotEnv, parseJson } from "@app/components/utilities/parseSecrets"; import { parseDotEnv, parseJson, parseYaml } from "@app/components/utilities/parseSecrets";
import { Button, Lottie, Modal, ModalContent } from "@app/components/v2"; import { Button, Lottie, Modal, ModalContent } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks"; import { usePopUp, useToggle } from "@app/hooks";
@ -127,7 +127,7 @@ export const SecretDropzone = ({
} }
}; };
const parseFile = (file?: File, isJson?: boolean) => { const parseFile = (file?: File) => {
const reader = new FileReader(); const reader = new FileReader();
if (!file) { if (!file) {
createNotification({ createNotification({
@ -140,10 +140,25 @@ export const SecretDropzone = ({
setIsLoading.on(); setIsLoading.on();
reader.onload = (event) => { reader.onload = (event) => {
if (!event?.target?.result) return; if (!event?.target?.result) return;
// parse function's argument looks like to be ArrayBuffer
const env = isJson let env: TParsedEnv;
? parseJson(event.target.result as ArrayBuffer)
: parseDotEnv(event.target.result as ArrayBuffer); const src = event.target.result as ArrayBuffer;
switch (file.type) {
case "application/json":
env = parseJson(src);
break;
case "text/yaml":
case "application/x-yaml":
case "application/yaml":
env = parseYaml(src);
break;
default:
env = parseDotEnv(src);
break;
}
setIsLoading.off(); setIsLoading.off();
handleParsedEnv(env); handleParsedEnv(env);
}; };
@ -165,12 +180,12 @@ export const SecretDropzone = ({
e.dataTransfer.dropEffect = "copy"; e.dataTransfer.dropEffect = "copy";
setDragActive.off(); setDragActive.off();
parseFile(e.dataTransfer.files[0], e.dataTransfer.files[0].type === "application/json"); parseFile(e.dataTransfer.files[0]);
}; };
const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => { const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault(); e.preventDefault();
parseFile(e.target?.files?.[0], e.target?.files?.[0]?.type === "application/json"); parseFile(e.target?.files?.[0]);
}; };
const handleSaveSecrets = async () => { const handleSaveSecrets = async () => {

View File

@ -13,9 +13,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v0.9.3 version: v0.9.4
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "v0.9.3" appVersion: "v0.9.4"

View File

@ -32,7 +32,7 @@ controllerManager:
- ALL - ALL
image: image:
repository: infisical/kubernetes-operator repository: infisical/kubernetes-operator
tag: v0.9.3 tag: v0.9.4
resources: resources:
limits: limits:
cpu: 500m cpu: 500m

View File

@ -5,7 +5,7 @@ go 1.21
require ( require (
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/aws/smithy-go v1.20.3 github.com/aws/smithy-go v1.20.3
github.com/infisical/go-sdk v0.4.4 github.com/infisical/go-sdk v0.5.97
github.com/lestrrat-go/jwx/v2 v2.1.4 github.com/lestrrat-go/jwx/v2 v2.1.4
github.com/onsi/ginkgo/v2 v2.6.0 github.com/onsi/ginkgo/v2 v2.6.0
github.com/onsi/gomega v1.24.1 github.com/onsi/gomega v1.24.1
@ -43,6 +43,7 @@ require (
github.com/google/s2a-go v0.1.7 // indirect github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.5 // indirect github.com/googleapis/gax-go/v2 v2.12.5 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/huandu/xstrings v1.5.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
@ -105,7 +106,7 @@ require (
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.32.0 golang.org/x/crypto v0.32.0
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.29.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect golang.org/x/term v0.28.0 // indirect

View File

@ -228,13 +228,15 @@ github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBY
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/infisical/go-sdk v0.4.4 h1:Z4CBzxfhiY6ikjRimOEeyEEnb3QT/BKw3OzNFH7Pe+U= github.com/infisical/go-sdk v0.5.97 h1:veOi6Hduda6emtwjdUI5SBg2qd2iDQc5xLKqZ15KSoM=
github.com/infisical/go-sdk v0.4.4/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= github.com/infisical/go-sdk v0.5.97/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@ -479,8 +481,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=