Atlassian – HoNoSoFt https://blog.honosoft.com Blog & Roll Fri, 11 Jan 2019 12:52:34 +0000 en-CA hourly 1 https://wordpress.org/?v=6.8.8 https://blog.honosoft.com/wp-content/uploads/2018/06/logo.png Atlassian – HoNoSoFt https://blog.honosoft.com 32 32 Canvas Chart ➡️ Snapshot ➡️ Manipulate ➡️ Send it to your confluence page (or elsewhere) https://blog.honosoft.com/2019/01/11/canvas-chart-%e2%9e%a1%ef%b8%8f-snapshot-%e2%9e%a1%ef%b8%8f-manipulate-%e2%9e%a1%ef%b8%8f-send-it-to-your-confluence-page-or-elsewhere/?utm_source=rss&utm_medium=rss&utm_campaign=canvas-chart-%25e2%259e%25a1%25ef%25b8%258f-snapshot-%25e2%259e%25a1%25ef%25b8%258f-manipulate-%25e2%259e%25a1%25ef%25b8%258f-send-it-to-your-confluence-page-or-elsewhere https://blog.honosoft.com/2019/01/11/canvas-chart-%e2%9e%a1%ef%b8%8f-snapshot-%e2%9e%a1%ef%b8%8f-manipulate-%e2%9e%a1%ef%b8%8f-send-it-to-your-confluence-page-or-elsewhere/#respond Fri, 11 Jan 2019 12:39:37 +0000 https://blog.honosoft.com/?p=426 Continue Reading]]> Today, let’s create a connection between one of your app using ChartJs + Confluence and send or synchronize a chart. The flow with Confluence server used will be with the basic authentication. In the case you want to build a real plugin/extension to Confluence, please follow the proper guide about it. In this blog post, the basic authentication is simply a mean to an end for quick prototyping.

A complete example of a standalone project can be found on Github (https://github.com/Nordes/HoNoSoFt.PushChartToConfluence.Sample).

Introduction

Like I said, we will have an application serving some charts using the Canvas (HTML5) technology. We will then resize, take a snapshot and then transmit this file to our backend server. Then it will connect to confluence, look at the existing attachments and if it exists it will update using a comment or in the case where the file does not exists, we will be posting a new file. It is nice to update the file, but why not add the file (image) to the page if it is not already there. 🙂

Proposal using a chart

Note that we’re going to add the chart, but the same flow could be used to also update some comment/text. As long as you are familiar with html syntax, you should be able to do something good.

Pre-requisites before starting

  1. If you already have a confluence server or follow the official installation guide from Confluence, skip to #3 (Create a small application)
  2. For the docker users, let’s go towards the evaluation “just because”
    1. docker pull atlassian/confluence-server:latest
      1. About 700mo
    2. While it download, you can start by requesting a KEY
      1. https://my.atlassian.com/license/evaluation
      2. Select a Confluence server license and keep that page open until the docker image is ready.
    3. Start the docker image
      • Command: docker run -v $volume$:/var/atlassian/application-data/confluence --name="confluence" -d -p 8090:8090 -p 8091:8091 atlassian/confluence-server
        • $volume$: C:/demo/your/confluence/home
      • Open site: http://localhost:8090/
    4. During installation, fetch the Server ID and input it in the evaluation page.
      1. Follow the instructions
        1. Select trial
        2. Get the Server ID
        3. Go on your atlassian page already open and paste it
        4. Generate your key
        5. Copy/paste your key in the form within the confluence page
        6. Click next (wait a little) and voilà
        7. Setup a user or two and then let’s start
  3. Create a small application with ChartJs (or use the one built in the previous post )

Back-End

In the back-end, we will first be receiving the image to at least test our local upload between front-end and back-end. After, we will start integrating the Confluence connexion.

Dotnet ImagesController: Receive Images (Part 1)

Within Dotnet Core, we can receive files or forms controls using the IFormCollection. I don’t think it is common to receive such thing, but when talking about receiving files, this comes handy. The client (JavaScript) will then create a collection and send it using a multipart/form-data. In there we can find many things, however what we will only using here are the files (((IFormCollection)MyFormCollection).Files).

Let’s create a controller named ImagesController we will receive the files as stream and then copy those files to a temporary folders. If you already played around with file streaming you can probably skip this part.

[Route("api/[controller]")]
[ApiController]
public ImagesController : ControllerBase {
    // .. some constructor stuff

    /// <summary>
    /// This will save the image in your local "temp" folder.
    /// </summary>
    [HttpPost]
    [ProducesResponseType((int)HttpStatusCode.Created)]
    public async Task<IActionResult> PostAsync(IFormCollection formCollection)
    {
        var files = formCollection.Files;
        long size = files.Sum(f => f.Length);
        List<string> fileList = new List<string>();

        foreach (var formFile in files)
        {
            // full path to file in temp location
            var filePath = Path.GetTempFileName();
            if (formFile.Length > 0)
            {
                using (var stream = new FileStream(filePath, FileMode.Create))
                {
                    // Save to file... We could remove the await, have a List<Task<..>> and then do a Task.WhenAll(myList)
                    await formFile.CopyToAsync(stream);
                    fileList.Add(filePath);
                }
            }
        }

        // Example of processed uploaded files returning details of the new file + original request.
        // You shouldn't rely on (or) trust the FileName property without validation.
        return Ok(new { count = files.Count, size, fileList });
    }
}

This is named Part 1 since we will come back in that code in order to add some code to send to confluence.

Front-End

Do the snapshot

From JavaScript, we have two methods that are quite handy to take snapshot of a Canvas. The first one is “toBlob(…)” and the second is “toDataUrl(…)“. While “toBlob” is not supported everywhere, I find it more useful for the demo, otherwise feel free to use “toDataUrl” for all browsers supports except Edge and then convert that base64 into a binary data (image/png). In case you go towards the “toDataUrl” method, don’t forget that you will have to transform the data once it’s server side for something readable (binary) as an image/png.

You will see in the next sub-section how to start from a ChartJs chart which you’d like to push to your back-end having a resolution of 1200px wide. Don’t forget that if you use an adaptive screen UX, it will not be displayed as 1200px until you do a snapshot for a fraction of a second.

How does the resizing work?

As you already know, a ChartJs canvas does not resize automatically at your wishes and if you want a snapshot, you are required to hack your way. Next, we are going to have the following flow:

Interesting part in the JS, I will skip the trivial step of generating a chart since you’re I suspect that you’re already able to do so.

// Some code (I used VueJs, but other language should look alike)

snapshot: function () {
  // Resize to desired size, bigger it is, more heavy will be the blob file.
  this.$refs.chartContainer.style.width = '1200px'
  this.chart.resize()
  var ctx = this

  // Do a snapshot
  this.$refs.chart.toBlob(function (blob) {
    // Resize back to original size
    ctx.$refs.chartContainer.style.width = ""
    ctx.chart.resize()

    // Prepare the form post
    // The file name used will be the chart title, but depending on your case, you
    // might want to have something more precise based on parameter (hash, encoding, something). 
    var filename = `${ctx.chartConfig.options.title.text}.png`
    var data = new FormData()
    data.append('file', blob, filename)

    const config = {
        headers: { 'content-type': 'multipart/form-data' }
    }

    // Post the file to your backend
    ctx.$http.post('/api/images', data, config)
    // Later in the article it should become as
    // ctx.$http.post('/api/images/confluence/{desiredPageId}', data, config)
  }, "image/png", 0.95);
}

// Some code

As you can see, I resize to 1200px => snapshot (toBlob) => resize back to original => send the file as multipart/form-data to the api.

The file should normally be created within your temp folder and the exact location will come back through the API. It is also part of the current data contract. Now that you have that, you can consider yourself ready for the next step, which is to transfer that buffered binary data directly to confluence using their API’s.

Back-end Part 2 – Use confluence API’s

Now that we know we can receive file and save it, we now simply need to use the Confluence API’s from Atlassian. The related documentation for that is:

API’s that we’re going to use for this demo are:

  • [GET] api/content/{pageId}/child/attachment?filename={formFile.FileName}&expand=version
    • Search/Retrieve the existing attachment. In case it does not exists, it will sends back an empty array.
  • [POST] api/content/{pageId}/child/attachment/{attachmentData.Id}/data
    • Update in case of existing attachment
  • [POST] api/content/{pageId}/child/attachment
    • Create the attachment resource if it was not already existing
  • [GET] api/content/{pageId}?expand=version,body.storage
    • Retrieve the details on the current page, especially the body specified in readable/editable way (storage). The version is also mandatory when you want to update the page.
  • [PUT] api/content/{pageId}
    • Api used in order to update the confluence page.

Update the ImagesController to forward to Confluence

There’s maybe more code than required in the controller. The proper approach would be to use a IDataProvider injected (for UT) and then implement the provider using the IHttpClientFactory. That way, all would be testable and also it would also put the logic where it should be. However, let’s put all for now in one place and please adapt for your needs.

// some code in the controller

        /// <summary>
        /// Receive 1 or more images to be sent to Confluence server.
        /// </summary>
        /// <param name="formCollection">The form data (only files are being consumed)</param>
        /// <param name="pageId">The confluence page Id</param>
        /// <remarks>
        /// More details can be found at https://developer.atlassian.com/server/confluence/confluence-rest-api-examples/
        /// </remarks>
        [HttpPost("confluence/{pageId}")]
        [ProducesResponseType((int)HttpStatusCode.Created)]
        public async Task<IActionResult> PostToConfluence(IFormCollection formCollection, int pageId)
        {
            var files = formCollection.Files;
            long size = files.Sum(f => f.Length);
            List<string> fileList = new List<string>();
            var forwardAttachmentTasks = new List<Task<FileTransferResult>>();

            foreach (var formFile in files)
            {
                if (formFile.Length > 0)
                {
                    forwardAttachmentTasks.Add(ForwardFileToConfluence(pageId, formFile));
                }
            }

            // In case we had multiple tasks at the same time.
            await Task.WhenAll(forwardAttachmentTasks.ToArray()).ConfigureAwait(false);
            await UpdatePage(pageId, forwardAttachmentTasks).ConfigureAwait(false);

            return StatusCode((int)HttpStatusCode.InternalServerError, new { count = files.Count, size, fileList });
        }

        private async Task UpdatePage(int pageId, List<Task<FileTransferResult>> forwardTasks)
        {
            if (forwardTasks.Any())
            {
                // Update the page
                var pageContentResult = await _confluenceHttpClient.GetAsync($"content/{pageId}?expand=version,body.storage");
                var pageContentData = JsonConvert.DeserializeObject<Models.Confluence.Content.ContentStorage>(await pageContentResult.Content.ReadAsStringAsync());

                string newContent = string.Empty;
                foreach (var sentAttachmentTask in forwardTasks)
                {
                    var sentAttachment = await sentAttachmentTask;
                    if (sentAttachment != null && sentAttachment.Results.Any())
                    {
                        // Add the file if not present on the page.
                        var fileName = sentAttachment.Results.First().Title;
                        if (pageContentData.Body.Storage.Value.IndexOf($"<ri:attachment ri:filename=\"{fileName}\" />") == -1)
                        {
                            newContent += $"<h2>You've just pushed: {fileName}</h2><p><ac:image><ri:attachment ri:filename=\"{fileName}\" /></ac:image></p>";
                        }
                        // Else: Nothing to do, it's already on the page somewhere.
                    }
                }

                // Update object (camelCase mandatory, so use a proper serializer in real life scenario)
                var updateQuery = new
                {
                    id = pageContentData.Id,
                    title = pageContentData.Title,
                    status = pageContentData.Status,
                    type = pageContentData.Type,
                    version = new { number = pageContentData.Version.Number + 1 },
                    body = new
                    {
                        storage = new
                        {
                            value = pageContentData.Body.Storage.Value + newContent,
                            representation = "storage"
                        }
                    }
                };

                var result = await _confluenceHttpClient.PutAsJsonAsync($"content/{pageId}", updateQuery);
            }
        }

        private async Task<FileTransferResult> ForwardFileToConfluence(int pageId, IFormFile formFile)
        {
            // Start getting if attachment exists
            var getIfAttachmentExists = _confluenceHttpClient.GetAsync($"content/{pageId}/child/attachment?filename={formFile.FileName}&expand=version").ConfigureAwait(false);
            // While previous request goes on, let's get the file.
            byte[] data;
            using (var br = new BinaryReader(formFile.OpenReadStream()))
            {
                data = br.ReadBytes((int)formFile.OpenReadStream().Length);
            }

            ByteArrayContent bytes = new ByteArrayContent(data);
            MultipartFormDataContent multipartContent = new MultipartFormDataContent();
            multipartContent.Add(bytes, "file", formFile.FileName);

            var attachmentRequestData = await getIfAttachmentExists;
            if (attachmentRequestData.IsSuccessStatusCode && attachmentRequestData.StatusCode == HttpStatusCode.OK)
            {
                // Page exists and no errors...
                var attachmentRequestContent = await attachmentRequestData.Content.ReadAsStringAsync().ConfigureAwait(false);
                var attachmentData = JsonConvert.DeserializeObject<FileSearch>(attachmentRequestContent);

                HttpResponseMessage putAttachmentResponse;
                // Update existing data.
                if (attachmentData.Size == 1)
                {
                    multipartContent.Add(new StringContent($"Automatic update/upload from TestApplication ;)."), "comment");
                    putAttachmentResponse = await _confluenceHttpClient.PostAsync(
                        $"content/{pageId}/child/attachment/{attachmentData.Results.First().Id}/data",
                        multipartContent);

                    // Result is 1 "item"
                    var content = await putAttachmentResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
                    var result = JsonConvert.DeserializeObject<Models.Confluence.Result>(content);

                    return new FileTransferResult() { Results = new Models.Confluence.Result[] { result }, Size = 1 };
                }
                else
                {
                    // Create the attachment
                    multipartContent.Add(new StringContent($"Automatic upload from TestApplication ;)."), "comment");
                    putAttachmentResponse = await _confluenceHttpClient.PostAsync(
                        $"content/{pageId}/child/attachment",
                        multipartContent);

                    // Result is a list of item.
                    var content = await putAttachmentResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
                    return JsonConvert.DeserializeObject<FileTransferResult>(content);
                }
            }

            return default(FileTransferResult);
        }

// some code in the controller

It’s a lot of code. By reading it, it should be really easy to understand. During the file upload to Atlassian Confluence, we add a comment (updated or created) and the version gets updated. That way you could show the changes over time.

What else you could do?

  • Transform the backend in order to have a provider injected
  • Create UT
  • Create IT
  • Change the token used in order to use the official Atlassian flow for plugins
  • Automate everything back-end by creating a scheduled job (not tested, but should be feasible)
    1. Selenium
    2. Image docker + chromium
    3. Execute the javascript using chrome Webdriver
    4. Send the image using a job.
  • Send a SnapShot (not necessarily from Charts) while building your backend app in a pipeline.

Conclusion

Thank you for reading and I hope you have learned something today, or at least enjoyed this article.

]]>
https://blog.honosoft.com/2019/01/11/canvas-chart-%e2%9e%a1%ef%b8%8f-snapshot-%e2%9e%a1%ef%b8%8f-manipulate-%e2%9e%a1%ef%b8%8f-send-it-to-your-confluence-page-or-elsewhere/feed/ 0
Tutorial: Bamboo ➕ NDepend => How to integrate them together https://blog.honosoft.com/2018/11/13/tutorial-bamboo-%e2%9e%95-ndepend-how-to-integrate-them-together/?utm_source=rss&utm_medium=rss&utm_campaign=tutorial-bamboo-%25e2%259e%2595-ndepend-how-to-integrate-them-together https://blog.honosoft.com/2018/11/13/tutorial-bamboo-%e2%9e%95-ndepend-how-to-integrate-them-together/#respond Tue, 13 Nov 2018 12:44:26 +0000 https://blog.honosoft.com/?p=348 Continue Reading]]> This tutorial is intended to help you to integrate NDepend within your current builds or your new builds. In cases you missed it, NDepend is a tool in order to validate your dependencies within your project. You can look at the previous post on HoNoSoFt (French). The topic of this tutorial will be discussing about are the following:

What is Bamboo?

Quickly speaking, Bamboo is the Build server from Atlassian in it’s CI stack. It is often used with the integration of BitBucket and it also offer the possibilities to connect to any other repositories (GitHub included). Bamboo itself allow you to create build project, deployment strategy, interconnection with other Atlassian tools or even send automatically a message with the build status to your #Slack channels. If you already played with Jenkins, AppVeyor or GitLab, you should be already familiar with the daily usage of such a tool. In order to get more details on Bamboo, please follow this link, it will give a full details on Bamboo.

Pre-Requisites

  • Bamboo Server (or Agent) in Windows. In case you need to install a new instance, click here to download Bamboo.
    • It is mandatory to use or install the JDK 1.8 (Or JRE also seems to works) in a folder without any spaces. Any version greater than 8 won’t be working. This is supposed to become supported in the future.
    • If you run a test server locally in order to look how it works, it will be accessible on http://localhost:8085/
    • During the first time wizard a key will be required for activation (30 days or real license). To obtain a 30 days license, go into your Atlassian space or simply use this link in order to request the evaluation license for your account
  • NDepend Build license or Trial active on the build machine (or your current machine, if you run Bamboo locally)
  • PowerShell is not mandatory, but you will see later that it is a must in case you want to have a proper build without failure

Bamboo – Add NDepend Capability

What is a capability in Bamboo

In Bamboo, in order to be able to use any build related tools, you should normally set it for each agent in order to add a specific capabilities. In this case, we will be adding the NDepend capability to a Windows agent. That capability will be pointing to the NDepend.Console.exe coming from the NDepend package previously installed/downloaded. Don’t forget to launch it at least once in order to trigger the license. The details are available in the documentation. In my case, it is installed into [D:\Ndepend\NDepend_2018.2.1.9119]. I prefer to keep the version within the folder name. This is easy to know if my agent is using the proper version of the tool. This capability, if I change the executable folder will affect automatically all the build that was using this capability. It will not, in any cases, re-trigger an automatic build. The variable will then be re-applied starting from the next build.

It is common to keep those capabilities or executable in that screen. It regroup all your agent(s) features. More details on this feature can be found on the Atlassian website.

⚠Be aware: The capabilities displays dependencies with existing builds. That being said, if a capability variable is used within a script, you won’t see anywhere that as an active dependency. For that reason, you are required, in case of multiple agents, to specify manually what Agent should be used for your build stage.

Go to your agent configuration

  1. Click on the cog (options wheel);
  2. Select Agents;
  3. This will make you jump in the administration panel directly on the Agents build resources;
  4. Select your any of your Windows build Agent by simply clicking it.

Agent summary

  1. Validate that your agent name is valid;
  2. Go within the Capabilities tab;
  3. Click on the Add capability link button.

Adding the capability

  1. Capability type: Executable
  2. Type: Command
  3. Executable label: NDepend
  4. Path: [D:\Ndepend\NDepend_2018.2.1.9119\NDepend.Console.exe]

See the Agent specific capabilities

As you can see, after doing the previous steps, you will have your Agent specific capability added. Now, we can use that capability during our build.

Bamboo Build configuration – Capabilities (Easy)

Let’s consider you have your build already existing and that you want to add the NDepend console command. In the current tutorial bellow I will be taking a simple Dotnet Core 2.1 solution. The build will be named IdentityServer4.LdapExtension (GitHub project), where you would have your Git repository, NDepend project and your basic build already configured (checkout and build).

Add a new task to your build stage

  1. Ensure you are in the configuration of your project;
  2. Go to your build job where you usually run your tests and everything else;
  3. Within the tasks, you will find all the detail of your current job;
  4. Add a new task.

Once the popup show,

  1. Search for Command in the search box;
  2. Select Command by clicking it.

Let’s configure the command

  1. The new command task should be already displayed;
  2. The description of the task, in this case: NDepend;
  3. Select the executable you created for your agent capability: NDepend;
  4. Enter the argument to the full path of your NDPROJ  and override the indirs/outdirs parameters, see the Jenkins article for more details. Those path can be relative if you configured your project accordingly, otherwise it will be the full path. In case you don’t set those path properly, NDepend is not able to find your PDB’s files and consequently, do a proper analysis.
    • Example: ${bamboo.build.working.directory}/IdentityServer.LdapExtension.ndproj . You can add a “/silent” is if you don’t want to see all the output logs from NDepend, but I strongly not advise to do this within a build simply because you might need to debug using the logs one day. The indirs/outdirs are omitted considering you have pre-configured your project with relative path instead of the default full path.
  5. Save your configuration

Build your new configuration

I recommend to always build after doing modification. That way it’s easy to see if you caused the build to fail for a real reason (configuration) or if it’s due to code change (triggered builds).

In the case of NDepend, it’s execution returns an exit code. Be aware, the exit code will make your build fails if there’s any quality gate errors. You can read the following when executing the console application:

Notice that NDepend.Console.exe returns a non-zero exit code when at least one Quality Gate fails. This exit code can be used to eventually stop your build process in such situation.

Ref.: https://www.ndepend.com/docs/ndepend-console

The report, even on failure, will exists, however the build will be red and your artifact will probably not be even available. Since there’s no “/ErrorLevel” custom configuration for the exit code, then you will have to do differently in order to ignore those errors.

  • Option 1: Ignore the errors by adding the special tags from NDepend within your code.
    • In that case, simply add the NDepend ignore attributes where required and re-launch a build after your commit.
  • Option 2: Use a script instead of a command in order to control your flow completely and to continue even if there’s a quality gate issue
    • This is the topic of the next section

Bamboo Build configuration – PowerShell Script (Complex)

As stated previously, this is more for those who already have a project having no big issue which cause a quality gate to fail, or if you want to have the Quality Gate failed but still want to generate the reports.

Don’t get me wrong, the Capability created was not for nothing, however, the only drawback in this case is that the Capability will not be detecting our build dependency to NDepend.

In this case, instead of creating a command, we will be creating a script (or add to current Dotnet Core build script)

  1. Go in your Build configuration, default stage (or specific one) and click on Add task;
  2. Select the Script type;
  3. Enter the description and select Windows PowerShell as the Interpreter;
  4. In this case, we will be using the Inline type instead of a PS1 shell script. This option will create automatically a shell script behind the scene. After, simply re-write the code-snippet bellow the image.

The PowerShell is using the existing NDproj (XML file) and then replace the InDirs by the current build path in the command line. This is for your NDproj file that you wouldn’t have any relative path. Once this is completed, it will create the final command and execute it (Invoke-Expression). Here’s the PowerShell script used in the above picture.

# NDProj should normally be a variable in the build, the same goes for other parts. That way
# you can use a build script instead of inline, and that build script can be part of your build
# repository.
$ndproj = "IdentityServer.LdapExtension.ndproj"

# Load the NDepend XML project file.
$ndependXml = [xml](Get-Content .\${ndproj})

# If you have any spaces withn your project, you might encounter issue
# be aware of that. Here the string is a "vanilla" path.
$inDirs = (( `
    $ndependXml.SelectNodes("//Dir") |
    where {$_."#Text" -like "*IdentityServer4.LdapExtension*"} | 
    select -ExpandProperty "#Text" )`
    -replace '^.*(\\IdentityServer4\.LdapExtension\\)',"${bamboo.build.working.directory}\")`
    -join " "

## Launch NDepend command
$expr = "${bamboo.capability.system.builder.command.NDepend} " +`
   "${bamboo.build.working.directory}\${ndproj} " +`
   "/InDirs ${inDirs} " +`
   "/OutDir ${bamboo.build.working.directory}\NDependOut"

Invoke-Expression $expr

# Simple output for the execution result.
write-host "The exit code was: ${LASTEXITCODE}"

Generate the NDepend Artifact

Now, you should be able to build and have the NDepend output folder. In this demo, we have NDependOut as the folder. Todo so, we will have to add the artifact configuration. Please, go back in the Build configuration and your default stage where you have the NDepend script. Once this is complete, select the Artifacts tab and click on Create artifact button.

The fields to input are:

  • Name: NDepend
  • Location: NDependOut
  • Copy pattern: **/*

Once this is completed, you should be able to re-build your project and see if your build artifact is there. Note that if you want to have comparisons between your builds, you could re-use the shared artifact. This is a bit more complex, but it’s possible and it won’t be explored in this tutorial. Let’s say that you could simply avoid cleaning up the build folder every new build. I don’t suggest to do that, but it’s something that could work out.

For example, we have the following successful build:

  1. Green build = success
  2. Go in your artifacts of your build
  3. Click on the artifact name that interest you (NDepend in this case)

Once the artifact will open, it will show the content of the ZIP file. Simply click on the file NDependReport.html. This will then open a link that looks like: http://localhost:8085/artifact/I4E-ID/shared/build-19/NDepend/NDependReport.html#Main

Your page should look similar to this (It depends actually on your project):

Conclusion

Thank you for reading, and I hope it helped you configure your build! If you wish to also integrate with SonarQube, you also can. You simply have to do what is proposed in SonarQube integration with NDepend article and in your Bamboo script, where you build your Dotnet project, you add the NDepend Sonar Runner. The result can then be seen after your build directly within SonarQube. Don’t forget that you might want to install some Marketplace plugin in Bamboo for Sonar in order to send the data. In case you don’t want to use a plugin, don’t worry, it is also possible.

]]>
https://blog.honosoft.com/2018/11/13/tutorial-bamboo-%e2%9e%95-ndepend-how-to-integrate-them-together/feed/ 0