Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Manage GitHub Apps via Terraform #509

Open
smauermann opened this issue Jul 7, 2020 · 22 comments
Open

[Feature Request] Manage GitHub Apps via Terraform #509

smauermann opened this issue Jul 7, 2020 · 22 comments
Labels
New resource Type: Feature New feature or request

Comments

@smauermann
Copy link

Hey Team,

I was looking for a way to manage GitHub apps [1] via Terraform but quickly realized there is none. It would be great to be able to setup a GitHub app, here are some exemplary settings one might want to manage:

  • app name
  • permissions
  • webhook URL and secret
  • event subscriptions

Cheers!

Terraform Version

Terraform v0.12.28

References

[1] https://developer.github.com/apps/about-apps/

@kfcampbell
Copy link
Member

It would be great to be able to setup a GitHub app

I've been playing around with this a little bit, and the public API support for GitHub Apps isn't where it needs to be in order to support standard CRUD operations on a GitHub App. I've spoken with the team internally and they're aware of the issue but it isn't an immediate priority.

The GraphQL API doesn't support App operations at the moment.

The auth used for creating Apps on the go-github package uses screen-scraping, which requires a One Time Password and which might rule out using it as part of the Terraform provider; I have some reservations about the stability of such a package.

In any event, I wanted to create a proof of concept, so I created a sample app with hard-coded values but I haven't been able to successfully get an App created. When skipping the OTP, I get a 406 Not Acceptable each time, and when attempting to provide a OTP through my standard 2FA app, the client panics when authenticating.

There might be something I'm missing on how the screen-scraping package of the google/go-github module is supposed to work. If there is, I'd love to hear about it!

In the meantime, we'll look into what operations we can support given an existing App.

@kfcampbell
Copy link
Member

I've investigated a little bit more on how to programmatically support the requested GitHub Apps operations.

  • app name

I can't find anything in the API documentation about changing the name. I don't think the API that works behind the UI is public.

  • permissions

Can be managed when creating an installation, but updating doesn't appear to be supported in the public API either.

  • webhook URL and secret

REST docs are here, but that functionality doesn't appear to be available in go-github yet.

  • event subscriptions

I believe these are managed through the same API as permissions above.

It definitely feels like we're working without tools on this feature a bit.

@jcudit
Copy link
Contributor

jcudit commented Aug 31, 2021

Thanks for scoping this out and reporting back to the rest of us on this. Definitely feels like the wrong time to be implementing automation around GitHub App creation as support for this via api.github.com is limited with no enhancements planned in the near-term.

My suggestion is we park this for now given the complexity of the creation path. However, we could pave paths for import-and-manage style workflows if there is more feedback from the community that this would be used.

@jcudit jcudit removed this from the v4.14.0 milestone Aug 31, 2021
@n3ph
Copy link

n3ph commented Apr 5, 2022

This would definitely be of huge value. We are heading towards of managing dozens of GitHub Apps in our organization.
For transparency reasons and documentation it would be great to have them as IaC.

Since github_app_installation_repository is already implemented, this would simply be awesome.

@calexandre
Copy link

I'm really craving for this feature...

@mwos-sl
Copy link

mwos-sl commented Sep 5, 2022

We manage tens of github apps across 3 orgs (github enterprise) in our company. Changing everything manually and keeping in sync is PITA.
E.g. recently I have to change "subscribe to events" in all apps, so I planned to write a tf module to manage our github app and automate that, but I was shocked there is no terraform support for it apparently :(

@kfcampbell
Copy link
Member

I've asked around internally and linked this issue along to the team. I know there's a lot of interest in this recorded. The team is looking at it but is not working on it presently and I can't offer a timeframe now, unfortunately.

@ericsampson
Copy link

Thank you for that @kfcampbell , there's definitely keen interest!

@suvl
Copy link

suvl commented Feb 2, 2023

Also, and I'm not sure if this is the right issue to ask for it, it'd be nice to have a fine grained token that'd allow to install one or more specific apps, while not allowing to install other apps, in some or all org repos. Having a GitOps operation where there's an admin PAT running code is dangerous and right now we are forced to run the tf apply manually on our own terminal, that's no good.

@ldevore-indigoag
Copy link

+1 this would be a lovely feature. Is there any update on if this might be explored?

@kfcampbell
Copy link
Member

I agree that this would be an excellent feature. I've seen some more people asking for it internally, which is good, but no idea on a timeframe for implementation/release yet unfortunately.

@jonesbusy
Copy link

+1 here. We also recently started to use github_enterprise_organization but now we miss managing app for those organization

@riccardofreixo
Copy link

+1 here. At my day job at $very_large_corp we could really use this. At the moment it introduces a manual step in an otherwise largely automated process.

@HakunMatat4
Copy link

+1 here

My team has to be able to add/remove thousands, yes, thousands of repositories from a given GitHub Application "Repository Access", having to manually perform this task is madness.

Terraform Integrations/github provider does support github_app_installation_repositories but that is all about it.

Does anybody know any workaround for now?? API calls or anything that can be somewhat automated??
This is a 2020 request and so far it looks like nothing was really done :(

Thanks

@MarconiGRF
Copy link

MarconiGRF commented Sep 26, 2023

+1 here

Spend a couple of hours today trying to achieve a behavior that depended on accesses managed by a Github app, if the feature was available the visibility would be in plain sight so the access could be managed easily in the IaC scenario I'm working on.

@peter-romfeld-bcw
Copy link

it seems there is a rest api for it now https://github.com/google/go-github/blob/master/github/apps.go#L16

@morremeyer
Copy link
Contributor

This part of the API has existed for some time, it can be used to manage app installations, but not apps

@phill-lewis
Copy link

+1 Registering interest in this one also - same as above it would be nice to fully automate this. Does anybody know where we can register interest in the required API change with github?

@kfcampbell
Copy link
Member

@phill-lewis it would be worth filing a request here.

@ShankyJS
Copy link

Come on GitHub, almost 4 years later and we still don't have access to this resource🥲

@dgellow
Copy link

dgellow commented Mar 29, 2024

For what it's worth, if you're using pulumi here is a simple dynamic provider to manage GitHub App info I've been using internally for the past 6 months or so:

import * as pulumi from '@pulumi/pulumi';
import { Octokit } from '@octokit/core';
import { AppAuthentication, createAppAuth } from '@octokit/auth-app';

export interface ConfigurationResourceInputs {
  appId: pulumi.Input<string>;
  privateKey: pulumi.Input<string>;
  clientId: pulumi.Input<string>;
  clientSecret: pulumi.Input<string>;
  url: pulumi.Input<string>;
  secret: pulumi.Input<string>;
  contentType?: pulumi.Input<string> | undefined;
}

export class Configuration extends pulumi.dynamic.Resource {
  public readonly appId!: pulumi.Output<pulumi.ID>;
  public readonly privateKey!: pulumi.Output<string>;
  public readonly clientId!: pulumi.Output<string>;
  public readonly clientSecret!: pulumi.Output<string>;
  public readonly url!: pulumi.Output<string>;
  public readonly secret!: pulumi.Output<string>;
  public readonly contentType!: pulumi.Output<string>;

  constructor(name: string, args: ConfigurationResourceInputs, opts?: pulumi.CustomResourceOptions) {
    super(new ConfigurationProvider(), name, args, opts, 'github-app', 'Configuration');
  }
}

interface ConfigurationInputs {
  appId: string;
  privateKey: string;
  clientId: string;
  clientSecret: string;
  url: string;
  secret: string;
  contentType?: string | undefined;
}

export interface ConfigurationOutputs extends Omit<ConfigurationInputs, 'url' | 'secret'> {
  url?: string | undefined;
  secret?: string | undefined;
}

export class ConfigurationProvider implements pulumi.dynamic.ResourceProvider {
  async auth({
    appId,
    privateKey,
    clientId,
    clientSecret,
  }: Pick<
    ConfigurationInputs,
    'appId' | 'clientId' | 'clientSecret' | 'privateKey'
  >): Promise<AppAuthentication> {
    const auth = createAppAuth({
      appId: appId,
      privateKey: privateKey,
      clientId: clientId,
      clientSecret: clientSecret,
    });
    return await auth({ type: 'app' });
  }

  async create(inputs: ConfigurationInputs): Promise<pulumi.dynamic.CreateResult> {
    const auth = await this.auth(inputs);
    const octokit = new Octokit({ auth: auth.token });
    try {
      const { data } = await octokit.request('PATCH /app/hook/config', {
        data: JSON.stringify({
          url: inputs.url,
          secret: inputs.secret,
          insecure_ssl: '0',
          content_type: inputs.contentType || 'json',
        }),
      });
      return {
        id: inputs.appId,
        outs: {
          appId: inputs.appId,
          privateKey: inputs.privateKey,
          clientId: inputs.clientId,
          clientSecret: inputs.clientSecret,
          url: data.url,
          secret: data.secret,
          contentType: data.content_type,
        },
      };
    } catch (err) {
      await pulumi.log.error(`${JSON.stringify(err, null, 2)}`);
      throw new Error('Failed to set github app webhook config');
    }
  }

  async diff(
    _id: pulumi.ID,
    olds: ConfigurationOutputs,
    news: ConfigurationInputs,
  ): Promise<pulumi.dynamic.DiffResult> {
    let changes: boolean = false;
    let replaces: string[] = [];
    let keys = Object.keys(news) as Array<Extract<keyof typeof news, string>>;

    for (const key of keys) {
      if (JSON.stringify(olds[key]) !== JSON.stringify(news[key])) {
        changes = true;
        if (key === 'appId' || key === 'privateKey' || key === 'clientId' || key === 'clientSecret') {
          replaces.push(key);
        }
      }
    }

    return { changes, replaces };
  }

  async update(
    id: pulumi.ID,
    _olds: ConfigurationOutputs,
    news: ConfigurationInputs,
  ): Promise<pulumi.dynamic.UpdateResult> {
    const auth = await this.auth(news);
    const octokit = new Octokit({ auth: auth.token });
    try {
      const { data } = await octokit.request('PATCH /app/hook/config', {
        data: JSON.stringify({
          url: news.url,
          secret: news.secret,
          insecure_ssl: '0',
          content_type: news.contentType || 'json',
        }),
      });
      return {
        outs: {
          appId: id,
          privateKey: news.privateKey,
          clientId: news.clientId,
          clientSecret: news.clientSecret,
          url: data.url,
          secret: data.secret,
          contentType: data.content_type,
        },
      };
    } catch (err) {
      await pulumi.log.error(`${JSON.stringify(err, null, 2)}`);
      throw new Error('Failed to set github app webhook config');
    }
  }

  async read(id: pulumi.ID, props?: ConfigurationOutputs): Promise<pulumi.dynamic.ReadResult> {
    if (!props) {
      throw new Error('Props undefined');
    }
    const auth = await this.auth({ ...props, appId: id });
    const octokit = new Octokit({ auth: auth.token });
    try {
      const { data } = await octokit.request('GET /app/hook/config');
      return {
        id,
        props: {
          appId: id,
          privateKey: props.privateKey,
          clientId: props.clientId,
          clientSecret: props.clientSecret,
          url: data.url,
          secret: data.secret,
          contentType: data.content_type,
        },
      };
    } catch (err) {
      await pulumi.log.error(`${JSON.stringify(err, null, 2)}`);
      throw new Error('Failed to set github app webhook config');
    }
  }
}

Just in case that would help others who were trying to figure out how to deal with that situation. Pulumi's GitHub provider is generated based on the terraform provider sources, so faces the same problem documented in this thread.

@IreshMM
Copy link

IreshMM commented Jun 28, 2024

+1. I would love to contribute as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New resource Type: Feature New feature or request
Projects
None yet
Development

No branches or pull requests